Merge "Import translations. DO NOT MERGE ANYWHERE" into tm-qpr-dev
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index f623295d..6e02390 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -402,7 +402,7 @@
mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
mCallingUid);
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivityForResult(intent, REQUEST_ADD_ACCOUNT);
+ startActivityForResult(new Intent(intent), REQUEST_ADD_ACCOUNT);
return;
}
} catch (OperationCanceledException e) {
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index bb26c46..1f095ac 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -727,9 +727,8 @@
* If invoked through a package other than a launcher app, returns an empty list.
*
* @param displayId the id of the logical display
- * @param packageName the name of the calling package
*/
- List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName);
+ List<DisplayInfo> getPossibleDisplayInfo(int displayId);
/**
* Called to show global actions.
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 20cdad4..baf9925 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -357,7 +357,7 @@
List<DisplayInfo> possibleDisplayInfos;
try {
possibleDisplayInfos = WindowManagerGlobal.getWindowManagerService()
- .getPossibleDisplayInfo(displayId, mContext.getPackageName());
+ .getPossibleDisplayInfo(displayId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index fcbe2b8..d956e9a 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -91,7 +91,7 @@
<string name="notification_channel_network_alert" msgid="4788053066033851841">"Alertas"</string>
<string name="notification_channel_call_forward" msgid="8230490317314272406">"Desvío de llamadas"</string>
<string name="notification_channel_emergency_callback" msgid="54074839059123159">"Modo de devolución de llamada de emergencia"</string>
- <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Estado de los datos móviles"</string>
+ <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Estado de datos móviles"</string>
<string name="notification_channel_sms" msgid="1243384981025535724">"Mensajes SMS"</string>
<string name="notification_channel_voice_mail" msgid="8457433203106654172">"Mensajes de voz"</string>
<string name="notification_channel_wfc" msgid="9048240466765169038">"Llamada por Wi-Fi"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 9b68186..57dcc90 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1949,13 +1949,13 @@
<string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"A configuración de Android TV non está dispoñible"</string>
<string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"A configuración da tableta non está dispoñible"</string>
<string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"A configuración do teléfono non está dispoñible"</string>
- <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo con Android TV."</string>
+ <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo Android TV."</string>
<string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde a tableta."</string>
<string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o teléfono."</string>
- <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o dispositivo con Android TV."</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o dispositivo Android TV."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esta aplicación solicita seguranza adicional. Proba a facelo desde a tableta."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o teléfono."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo con Android TV."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo Android TV."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde a tableta."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o teléfono."</string>
<string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación deseñouse para unha versión anterior de Android e quizais non funcione correctamente. Proba a buscar actualizacións ou contacta co programador."</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 251a53a..6b80aa3 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -260,7 +260,7 @@
<string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Stille modus"</string>
<string name="global_action_silent_mode_on_status" msgid="2371892537738632013">"Geluid is UIT"</string>
<string name="global_action_silent_mode_off_status" msgid="6608006545950920042">"Geluid is AAN"</string>
- <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Vliegtuigmodus"</string>
+ <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Vliegtuigmodus"</string>
<string name="global_actions_airplane_mode_on_status" msgid="5508025516695361936">"Vliegtuigmodus is AAN"</string>
<string name="global_actions_airplane_mode_off_status" msgid="8522219771500505475">"Vliegtuigmodus is UIT"</string>
<string name="global_action_settings" msgid="4671878836947494217">"Instellingen"</string>
@@ -990,7 +990,7 @@
<string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"Gebruikersselectie"</string>
<string name="keyguard_accessibility_status" msgid="6792745049712397237">"Status"</string>
<string name="keyguard_accessibility_camera" msgid="7862557559464986528">"Camera"</string>
- <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Mediabediening"</string>
+ <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Mediabediening"</string>
<string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"Opnieuw indelen van widget gestart."</string>
<string name="keyguard_accessibility_widget_reorder_end" msgid="1083806817600593490">"Opnieuw indelen van widget beëindigd."</string>
<string name="keyguard_accessibility_widget_deleted" msgid="1509738950119878705">"Widget <xliff:g id="WIDGET_INDEX">%1$s</xliff:g> verwijderd."</string>
@@ -1482,7 +1482,7 @@
<string name="forward_intent_to_work" msgid="3620262405636021151">"U gebruikt deze app in je werkprofiel"</string>
<string name="input_method_binding_label" msgid="1166731601721983656">"Invoermethode"</string>
<string name="sync_binding_label" msgid="469249309424662147">"Synchroniseren"</string>
- <string name="accessibility_binding_label" msgid="1974602776545801715">"Toegankelijkheid"</string>
+ <string name="accessibility_binding_label" msgid="1974602776545801715">"Toegankelijkheid"</string>
<string name="wallpaper_binding_label" msgid="1197440498000786738">"Achtergrond"</string>
<string name="chooser_wallpaper" msgid="3082405680079923708">"Achtergrond wijzigen"</string>
<string name="notification_listener_binding_label" msgid="2702165274471499713">"Listener voor meldingen"</string>
@@ -1986,7 +1986,7 @@
<string name="app_category_news" msgid="1172762719574964544">"Nieuws en tijdschriften"</string>
<string name="app_category_maps" msgid="6395725487922533156">"Maps en navigatie"</string>
<string name="app_category_productivity" msgid="1844422703029557883">"Productiviteit"</string>
- <string name="app_category_accessibility" msgid="6643521607848547683">"Toegankelijkheid"</string>
+ <string name="app_category_accessibility" msgid="6643521607848547683">"Toegankelijkheid"</string>
<string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"Apparaatopslag"</string>
<string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"USB-foutopsporing"</string>
<string name="time_picker_hour_label" msgid="4208590187662336864">"uur"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 26bfeb4..dfb64e2 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -90,7 +90,7 @@
<string name="notification_channel_network_alert" msgid="4788053066033851841">"ଆଲର୍ଟ"</string>
<string name="notification_channel_call_forward" msgid="8230490317314272406">"କଲ୍ ଫରୱାର୍ଡିଂ"</string>
<string name="notification_channel_emergency_callback" msgid="54074839059123159">"ଜରୁରୀକାଳୀନ କଲବ୍ୟାକ୍ ମୋଡ୍"</string>
- <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"ମୋବାଇଲ୍ ଡାଟା ଷ୍ଟାଟସ୍"</string>
+ <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"ମୋବାଇଲ ଡାଟା ଷ୍ଟାଟସ"</string>
<string name="notification_channel_sms" msgid="1243384981025535724">"SMS ମେସେଜ୍"</string>
<string name="notification_channel_voice_mail" msgid="8457433203106654172">"ଭଏସମେଲ୍ ମେସେଜ୍"</string>
<string name="notification_channel_wfc" msgid="9048240466765169038">"ୱାଇ-ଫାଇ କଲିଙ୍ଗ"</string>
@@ -799,30 +799,30 @@
<item msgid="8150904584178569699">"ୱାର୍କ ଫ୍ୟାକ୍ସ"</item>
<item msgid="4537253139152229577">"ହୋମ ଫାକ୍ସ"</item>
<item msgid="6751245029698664340">"ପେଜର୍"</item>
- <item msgid="1692790665884224905">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="1692790665884224905">"ଅନ୍ୟ"</item>
<item msgid="6216981255272016212">"କଷ୍ଟମ୍"</item>
</string-array>
<string-array name="emailAddressTypes">
<item msgid="7786349763648997741">"ହୋମ"</item>
<item msgid="435564470865989199">"ୱାର୍କ"</item>
- <item msgid="4199433197875490373">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="4199433197875490373">"ଅନ୍ୟ"</item>
<item msgid="3233938986670468328">"କଷ୍ଟମ୍"</item>
</string-array>
<string-array name="postalAddressTypes">
<item msgid="3861463339764243038">"ହୋମ"</item>
<item msgid="5472578890164979109">"ୱାର୍କ"</item>
- <item msgid="5718921296646594739">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="5718921296646594739">"ଅନ୍ୟ"</item>
<item msgid="5523122236731783179">"କଷ୍ଟମ୍"</item>
</string-array>
<string-array name="imAddressTypes">
<item msgid="588088543406993772">"ହୋମ"</item>
<item msgid="5503060422020476757">"ୱାର୍କ"</item>
- <item msgid="2530391194653760297">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="2530391194653760297">"ଅନ୍ୟ"</item>
<item msgid="7640927178025203330">"କଷ୍ଟମ୍"</item>
</string-array>
<string-array name="organizationTypes">
<item msgid="6144047813304847762">"ୱାର୍କ"</item>
- <item msgid="7402720230065674193">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="7402720230065674193">"ଅନ୍ୟ"</item>
<item msgid="808230403067569648">"କଷ୍ଟମ୍"</item>
</string-array>
<string-array name="imProtocols">
@@ -842,7 +842,7 @@
<string name="phoneTypeFaxWork" msgid="6757519896109439123">"ୱାର୍କ ଫାକ୍ସ"</string>
<string name="phoneTypeFaxHome" msgid="6678559953115904345">"ହୋମ ଫାକ୍ସ"</string>
<string name="phoneTypePager" msgid="576402072263522767">"ପେଜର୍"</string>
- <string name="phoneTypeOther" msgid="6918196243648754715">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="phoneTypeOther" msgid="6918196243648754715">"ଅନ୍ୟ"</string>
<string name="phoneTypeCallback" msgid="3455781500844157767">"କଲବ୍ୟାକ୍"</string>
<string name="phoneTypeCar" msgid="4604775148963129195">"କାର୍"</string>
<string name="phoneTypeCompanyMain" msgid="4482773154536455441">"କମ୍ପାନୀର ମୁଖ୍ୟ"</string>
@@ -863,16 +863,16 @@
<string name="emailTypeCustom" msgid="1809435350482181786">"କଷ୍ଟମ୍"</string>
<string name="emailTypeHome" msgid="1597116303154775999">"ହୋମ"</string>
<string name="emailTypeWork" msgid="2020095414401882111">"ୱାର୍କ"</string>
- <string name="emailTypeOther" msgid="5131130857030897465">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="emailTypeOther" msgid="5131130857030897465">"ଅନ୍ୟ"</string>
<string name="emailTypeMobile" msgid="787155077375364230">"ମୋବାଇଲ୍"</string>
<string name="postalTypeCustom" msgid="5645590470242939129">"କଷ୍ଟମ୍"</string>
<string name="postalTypeHome" msgid="7562272480949727912">"ହୋମ"</string>
<string name="postalTypeWork" msgid="8553425424652012826">"ୱାର୍କ"</string>
- <string name="postalTypeOther" msgid="7094245413678857420">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="postalTypeOther" msgid="7094245413678857420">"ଅନ୍ୟ"</string>
<string name="imTypeCustom" msgid="5653384545085765570">"କଷ୍ଟମ୍"</string>
<string name="imTypeHome" msgid="6996507981044278216">"ହୋମ"</string>
<string name="imTypeWork" msgid="2099668940169903123">"ୱାର୍କ"</string>
- <string name="imTypeOther" msgid="8068447383276219810">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="imTypeOther" msgid="8068447383276219810">"ଅନ୍ୟ"</string>
<string name="imProtocolCustom" msgid="4437878287653764692">"କଷ୍ଟମ୍"</string>
<string name="imProtocolAim" msgid="4050198236506604378">"AIM"</string>
<string name="imProtocolMsn" msgid="2257148557766499232">"Windows Live"</string>
@@ -884,7 +884,7 @@
<string name="imProtocolJabber" msgid="7919269388889582015">"Jabber"</string>
<string name="imProtocolNetMeeting" msgid="4985002408136148256">"NetMeeting"</string>
<string name="orgTypeWork" msgid="8684458700669564172">"ୱାର୍କ"</string>
- <string name="orgTypeOther" msgid="5450675258408005553">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="orgTypeOther" msgid="5450675258408005553">"ଅନ୍ୟ"</string>
<string name="orgTypeCustom" msgid="1126322047677329218">"କଷ୍ଟମ୍"</string>
<string name="relationTypeCustom" msgid="282938315217441351">"କଷ୍ଟମ୍"</string>
<string name="relationTypeAssistant" msgid="4057605157116589315">"Assistant"</string>
@@ -904,7 +904,7 @@
<string name="sipAddressTypeCustom" msgid="6283889809842649336">"କଷ୍ଟମ୍"</string>
<string name="sipAddressTypeHome" msgid="5918441930656878367">"ହୋମ"</string>
<string name="sipAddressTypeWork" msgid="7873967986701216770">"ୱାର୍କ"</string>
- <string name="sipAddressTypeOther" msgid="6317012577345187275">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="sipAddressTypeOther" msgid="6317012577345187275">"ଅନ୍ୟ"</string>
<string name="quick_contacts_not_available" msgid="1262709196045052223">"ଏହି କଣ୍ଟାକ୍ଟ ଦେଖିବାକୁ କୌଣସି ଆପ୍ଲିକେସନ ମିଳିଲା ନାହିଁ।"</string>
<string name="keyguard_password_enter_pin_code" msgid="6401406801060956153">"PIN କୋଡ୍ ଟାଇପ୍ କରନ୍ତୁ"</string>
<string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"PUK ଓ ନୂଆ PIN କୋଡ୍ ଟାଇପ୍ କରନ୍ତୁ"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index c2fe6dd..318971d 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -92,7 +92,7 @@
<string name="notification_channel_network_alert" msgid="4788053066033851841">"Оповещения"</string>
<string name="notification_channel_call_forward" msgid="8230490317314272406">"Переадресация вызовов"</string>
<string name="notification_channel_emergency_callback" msgid="54074839059123159">"Режим экстренных обратных вызовов"</string>
- <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Состояние мобильного Интернета"</string>
+ <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Состояние мобильного интернета"</string>
<string name="notification_channel_sms" msgid="1243384981025535724">"SMS"</string>
<string name="notification_channel_voice_mail" msgid="8457433203106654172">"Голосовые сообщения"</string>
<string name="notification_channel_wfc" msgid="9048240466765169038">"Звонки по Wi-Fi"</string>
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index dfce501..3d50d22 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Laat los"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Program sal dalk nie met verdeelde skerm werk nie."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Program steun nie verdeelde skerm nie."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Hierdie app kan net in 1 venster oopgemaak word."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Program sal dalk nie op \'n sekondêre skerm werk nie."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Program steun nie begin op sekondêre skerms nie."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skermverdeler"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 655e53f..a0213f4 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"দেখুৱাওক"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"এপ্টোৱে বিভাজিত স্ক্ৰীনৰ সৈতে কাম নকৰিব পাৰে।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"এপ্টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে।"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই এপ্টো কেৱল ১ খন ৱিণ্ড’ত খুলিব পাৰি।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"গৌণ ডিছপ্লেত এপে সঠিকভাৱে কাম নকৰিব পাৰে।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index d3d2058..f842bfe 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Güvənli məkandan çıxarın"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Tətbiq bölünmüş ekran ilə işləməyə bilər."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Tətbiq ekran bölünməsini dəstəkləmir."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu tətbiq yalnız 1 pəncərədə açıla bilər."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Tətbiq ikinci ekranda işləməyə bilər."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Tətbiq ikinci ekranda başlamağı dəstəkləmir."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcısı"</string>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 839ecb6..540ae7c 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Uklonite iz tajne memorije"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi sa podeljenim ekranom."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podeljeni ekran."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija može da se otvori samo u jednom prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionisati na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdelnik podeljenog ekrana"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index b227cf1..bea7538 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Паказаць"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Праграма можа не працаваць у рэжыме падзеленага экрана."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Праграма не падтрымлівае функцыю дзялення экрана."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Гэту праграму можна адкрыць толькі ў адным акне."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Праграма можа не працаваць на дадатковых экранах."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Праграма не падтрымлівае запуск на дадатковых экранах."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Раздзяляльнік падзеленага экрана"</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 20f4038..59915e6 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Отмяна на съхраняването"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Приложението може да не работи в режим на разделен екран."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложението не поддържа разделен екран."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Това приложение може да се отвори само в 1 прозорец."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Възможно е приложението да не работи на алтернативни дисплеи."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложението не поддържа използването на алтернативни дисплеи."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделител в режима за разделен екран"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 4add02e..63c9684 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"আনস্ট্যাস করুন"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"অ্যাপটি স্প্লিট স্ক্রিনে কাজ নাও করতে পারে।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"অ্যাপ্লিকেশান বিভক্ত-স্ক্রিন সমর্থন করে না৷"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই অ্যাপটি শুধু ১টি উইন্ডোয় খোলা যেতে পারে।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"সেকেন্ডারি ডিসপ্লেতে অ্যাপটি কাজ নাও করতে পারে।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"সেকেন্ডারি ডিসপ্লেতে অ্যাপ লঞ্চ করা যাবে না।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"বিভক্ত-স্ক্রিন বিভাজক"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 1444b89..b725efe 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vađenje iz stasha"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi na podijeljenom ekranu."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava dijeljenje ekrana."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija se može otvoriti samo u 1 prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće raditi na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog ekrana"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index a0bec42..4383916 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Deixa d\'amagar"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"És possible que l\'aplicació no funcioni amb la pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'aplicació no admet la pantalla dividida."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aquesta aplicació només pot obrir-se en 1 finestra."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"És possible que l\'aplicació no funcioni en una pantalla secundària."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'aplicació no es pot obrir en pantalles secundàries."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalles"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 332e956..e5cb26f 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušit uložení"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikace v režimu rozdělené obrazovky nemusí fungovat."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikace nepodporuje režim rozdělené obrazovky."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tuto aplikaci lze otevřít jen na jednom okně."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikace na sekundárním displeji nemusí fungovat."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikace nepodporuje spuštění na sekundárních displejích."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Čára rozdělující obrazovku"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index a7fb175..46f7c69 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vis"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Appen fungerer muligvis ikke i opdelt skærm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen understøtter ikke opdelt skærm."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne app kan kun åbnes i 1 vindue."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer muligvis ikke på sekundære skærme."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke åbnes på sekundære skærme."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Adskiller til opdelt skærm"</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 8d69eb4..1269d36 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Aus Stash entfernen"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Die App funktioniert unter Umständen im Modus für geteilten Bildschirm nicht."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Das Teilen des Bildschirms wird in dieser App nicht unterstützt."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Diese App kann nur in einem einzigen Fenster geöffnet werden."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Die App funktioniert auf einem sekundären Display möglicherweise nicht."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Die App unterstützt den Start auf sekundären Displays nicht."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bildschirmteiler"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 613e5b0..f8a69ef 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Κατάργηση απόκρυψης"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Η εφαρμογή ενδέχεται να μην λειτουργεί με διαχωρισμό οθόνης."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Αυτή η εφαρμογή μπορεί να ανοιχθεί μόνο σε 1 παράθυρο."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Η εφαρμογή ίσως να μην λειτουργήσει σε δευτερεύουσα οθόνη."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Η εφαρμογή δεν υποστηρίζει την εκκίνηση σε δευτερεύουσες οθόνες."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Διαχωριστικό οθόνης"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 5619617..8e46c3e 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 9f15669..7cbbf64 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in 1 window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split-screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 5619617..8e46c3e 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 5619617..8e46c3e 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 2b474db..b2720be 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in 1 window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split-screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 67386c4..47445a7 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Dejar de almacenar de manera segura"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la app no funcione en el modo de pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La app no es compatible con la función de pantalla dividida."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app solo puede estar abierta en 1 ventana."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la app no funcione en una pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La app no puede iniciarse en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index d4a6b6c..6c45231 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"No esconder"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la aplicación no funcione con la pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La aplicación no admite la pantalla dividida."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación solo puede abrirse en una ventana."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la aplicación no funcione en una pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La aplicación no se puede abrir en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Dividir la pantalla"</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index df84e7c..a8dc08c 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Eemalda hoidlast"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Rakendus ei pruugi poolitatud ekraaniga töötada."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Rakendus ei toeta jagatud ekraani."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Selle rakenduse saab avada ainult ühes aknas."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Rakendus ei pruugi teisesel ekraanil töötada."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Rakendus ei toeta teisestel ekraanidel käivitamist."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ekraanijagaja"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index c062b92..e7cb5f4 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"لغو مخفیسازی"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن است برنامه با «صفحهٔ دونیمه» کار نکند."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"برنامه از تقسیم صفحه پشتیبانی نمیکند."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"این برنامه فقط در ۱ پنجره میتواند باز شود."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن است برنامه در نمایشگر ثانویه کار نکند."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"برنامه از راهاندازی در نمایشگرهای ثانویه پشتیبانی نمیکند."</string>
<string name="accessibility_divider" msgid="703810061635792791">"تقسیمکننده صفحه"</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 6efb770..86199f3 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poista turvasäilytyksestä"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Sovellus ei ehkä toimi jaetulla näytöllä."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Sovellus ei tue jaetun näytön tilaa."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tämän sovelluksen voi avata vain yhdessä ikkunassa."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Sovellus ei ehkä toimi toissijaisella näytöllä."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Sovellus ei tue käynnistämistä toissijaisilla näytöillä."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Näytön jakaja"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 0c2e1b1..1f3ac9e 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Retirer de la réserve"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'application n\'est pas compatible avec l\'écran partagé."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette application ne peut être ouverte que dans une fenêtre."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index b714718..f1dbb35 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Application incompatible avec l\'écran partagé."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette appli ne peut être ouverte que dans 1 fenêtre."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 952d532..6e215a1 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Non esconder"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Pode que a aplicación non funcione coa pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A aplicación non é compatible coa función de pantalla dividida."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación só se pode abrir en 1 ventá."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É posible que a aplicación non funcione nunha pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A aplicación non se pode iniciar en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 67488f9..1446e70 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poništite sakrivanje"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće funkcionirati s podijeljenim zaslonom."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podijeljeni zaslon."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova se aplikacija može otvoriti samo u jednom prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionirati na sekundarnom zaslonu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim zaslonima."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog zaslona"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 41d2d317..221c329 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Félretevés megszüntetése"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Lehet, hogy az alkalmazás nem működik osztott képernyős nézetben."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Az alkalmazás nem támogatja az osztott képernyős nézetet."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ez az alkalmazás csak egy ablakban nyitható meg."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Előfordulhat, hogy az alkalmazás nem működik másodlagos kijelzőn."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Az alkalmazást nem lehet másodlagos kijelzőn elindítani."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Elválasztó az osztott nézetben"</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 502bfb1..7be9941 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ցուցադրել"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Հավելվածը չի կարող աշխատել տրոհված էկրանի ռեժիմում։"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Հավելվածը չի աջակցում էկրանի տրոհումը:"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Այս հավելվածը հնարավոր է բացել միայն մեկ պատուհանում։"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Հավելվածը կարող է չաշխատել լրացուցիչ էկրանի վրա"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Հավելվածը չի աջակցում գործարկումը լրացուցիչ էկրանների վրա"</string>
<string name="accessibility_divider" msgid="703810061635792791">"Տրոհված էկրանի բաժանիչ"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 561df72..4e760ef 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Batalkan stash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikasi mungkin tidak berfungsi dengan layar terpisah."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App tidak mendukung layar terpisah."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplikasi ini hanya dapat dibuka di 1 jendela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikasi mungkin tidak berfungsi pada layar sekunder."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikasi tidak mendukung peluncuran pada layar sekunder."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pembagi layar terpisah"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 355968b..50d4ee7 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Taka úr geymslu"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Hugsanlega virkar forritið ekki með skjáskiptingu."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Forritið styður ekki að skjánum sé skipt."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aðeins er hægt að opna þetta forrit í 1 glugga."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Hugsanlegt er að forritið virki ekki á öðrum skjá."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Forrit styður ekki opnun á öðrum skjá."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skjáskipting"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 1417d00..d2595f7 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Annulla accantonamento"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"L\'app potrebbe non funzionare con lo schermo diviso."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'app non supporta la modalità Schermo diviso."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Questa app può essere aperta soltanto in 1 finestra."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"L\'app potrebbe non funzionare su un display secondario."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'app non supporta l\'avvio su display secondari."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Strumento per schermo diviso"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index b3c0b65..883596e 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ביטול ההסתרה הזמנית"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ייתכן שהאפליקציה לא תפעל במסך מפוצל."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"האפליקציה אינה תומכת במסך מפוצל."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ניתן לפתוח את האפליקציה הזו רק בחלון אחד."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ייתכן שהאפליקציה לא תפעל במסך משני."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"האפליקציה אינה תומכת בהפעלה במסכים משניים."</string>
<string name="accessibility_divider" msgid="703810061635792791">"מחלק מסך מפוצל"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index a5b0f21..6bb22a2 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"表示"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"アプリは分割画面では動作しないことがあります。"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"アプリで分割画面がサポートされていません。"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"このアプリはウィンドウが 1 つの場合のみ開くことができます。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"アプリはセカンダリ ディスプレイでは動作しないことがあります。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"アプリはセカンダリ ディスプレイでの起動に対応していません。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分割画面の分割線"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index a85efb4..6cf7d78 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"გადანახვის გაუქმება"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"აპმა შეიძლება არ იმუშაოს გაყოფილი ეკრანის რეჟიმში."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ამ აპის გახსნა შესაძლებელია მხოლოდ 1 ფანჯარაში."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"აპმა შეიძლება არ იმუშაოს მეორეულ ეკრანზე."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"აპს არ გააჩნია მეორეული ეკრანის მხარდაჭერა."</string>
<string name="accessibility_divider" msgid="703810061635792791">"გაყოფილი ეკრანის რეჟიმის გამყოფი"</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 9c0c15c..216619a 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Көрсету"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Қолданба экранды бөлу режимінде жұмыс істемеуі мүмкін."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Қодланба бөлінген экранды қолдамайды."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бұл қолданбаны тек 1 терезеден ашуға болады."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Қолданба қосымша дисплейде жұмыс істемеуі мүмкін."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Қолданба қосымша дисплейлерде іске қосуды қолдамайды."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Бөлінген экран бөлгіші"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index d56d6a5..79aca62 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ឈប់លាក់ជាបណ្ដោះអាសន្ន"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"កម្មវិធីអាចនឹងមិនដំណើរការជាមួយមុខងារបំបែកអេក្រង់ទេ។"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"កម្មវិធីមិនគាំទ្រអេក្រង់បំបែកជាពីរទេ"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"កម្មវិធីនេះអាចបើកនៅក្នុងវិនដូតែ 1 ប៉ុណ្ណោះ។"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"កម្មវិធីនេះប្រហែលជាមិនដំណើរការនៅលើអេក្រង់បន្ទាប់បន្សំទេ។"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"កម្មវិធីនេះមិនអាចចាប់ផ្តើមនៅលើអេក្រង់បន្ទាប់បន្សំបានទេ។"</string>
<string name="accessibility_divider" msgid="703810061635792791">"កម្មវិធីចែកអេក្រង់បំបែក"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 1b31a6f..9e9333e 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ಅನ್ಸ್ಟ್ಯಾಶ್ ಮಾಡಿ"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ವಿಭಜಿಸಿದ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ಆ್ಯಪ್ ಕೆಲಸ ಮಾಡದೇ ಇರಬಹುದು."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ಅಪ್ಲಿಕೇಶನ್ ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ಈ ಆ್ಯಪ್ ಅನ್ನು 1 ವಿಂಡೋದಲ್ಲಿ ಮಾತ್ರ ತೆರೆಯಬಹುದು."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ಸೆಕೆಂಡರಿ ಡಿಸ್ಪ್ಲೇಗಳಲ್ಲಿ ಅಪ್ಲಿಕೇಶನ್ ಕಾರ್ಯ ನಿರ್ವಹಿಸದೇ ಇರಬಹುದು."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ಸೆಕೆಂಡರಿ ಡಿಸ್ಪ್ಲೇಗಳಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
<string name="accessibility_divider" msgid="703810061635792791">"ಸ್ಪ್ಲಿಟ್-ಪರದೆ ಡಿವೈಡರ್"</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 563db5b..f9b495a 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"숨기기 취소"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"앱이 분할 화면에서 작동하지 않을 수 있습니다."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"앱이 화면 분할을 지원하지 않습니다."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"이 앱은 창 1개에서만 열 수 있습니다."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"앱이 보조 디스플레이에서 작동하지 않을 수도 있습니다."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"앱이 보조 디스플레이에서의 실행을 지원하지 않습니다."</string>
<string name="accessibility_divider" msgid="703810061635792791">"화면 분할기"</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 0497d15..a57f9ae 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Сейфтен чыгаруу"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Колдонмодо экран бөлүнбөшү мүмкүн."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Колдонмодо экран бөлүнбөйт."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бул колдонмону 1 терезеде гана ачууга болот."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Колдонмо кошумча экранда иштебей коюшу мүмкүн."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Колдонмону кошумча экрандарда иштетүүгө болбойт."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Экранды бөлгүч"</string>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 35aecaa..8e42aa3 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ເອົາອອກຈາກບ່ອນເກັບສ່ວນຕົວ"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ແອັບອາດໃຊ້ບໍ່ໄດ້ກັບການແບ່ງໜ້າຈໍ."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ແອັບບໍ່ຮອງຮັບໜ້າຈໍແບບແຍກກັນ."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ແອັບນີ້ສາມາດເປີດໄດ້ໃນ 1 ໜ້າຈໍເທົ່ານັ້ນ."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ແອັບອາດບໍ່ສາມາດໃຊ້ໄດ້ໃນໜ້າຈໍທີສອງ."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ແອັບບໍ່ຮອງຮັບການເປີດໃນໜ້າຈໍທີສອງ."</string>
<string name="accessibility_divider" msgid="703810061635792791">"ຕົວຂັ້ນການແບ່ງໜ້າຈໍ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index f4ed192..dc99690 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Nebeslėpti"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Programa gali neveikti naudojant išskaidyto ekrano režimą."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programoje nepalaikomas skaidytas ekranas."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šią programą galima atidaryti tik viename lange."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Programa gali neveikti antriniame ekrane."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programa nepalaiko paleisties antriniuose ekranuose."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skaidyto ekrano daliklis"</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index ffb9ddb..bd2eef4 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Rādīt"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Iespējams, lietotne nedarbosies ekrāna sadalīšanas režīmā."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Lietotnē netiek atbalstīta ekrāna sadalīšana."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šo lietotni var atvērt tikai vienā logā."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Lietotne, iespējams, nedarbosies sekundārajā displejā."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Lietotnē netiek atbalstīta palaišana sekundārajos displejos."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ekrāna sadalītājs"</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index cf6b9d5..d133654 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Прикажете"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Апликацијата може да не работи со поделен екран."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликацијата не поддржува поделен екран."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Апликацијава може да се отвори само во еден прозорец."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликацијата може да не функционира на друг екран."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликацијата не поддржува стартување на други екрани."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделник на поделен екран"</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 54513d18..264f9a0 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ил гаргах"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Апп хуваагдсан дэлгэц дээр ажиллахгүй байж болзошгүй."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Энэ апп нь дэлгэц хуваах тохиргоог дэмждэггүй."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Энэ аппыг зөвхөн 1 цонхонд нээх боломжтой."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апп хоёрдогч дэлгэцэд ажиллахгүй."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Аппыг хоёрдогч дэлгэцэд эхлүүлэх боломжгүй."</string>
<string name="accessibility_divider" msgid="703810061635792791">"\"Дэлгэц хуваах\" хуваагч"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 2833480..7a475ed 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्टॅश करा"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"अॅप कदाचित स्प्लिट स्क्रीनसह काम करू शकत नाही."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अॅप स्क्रीन-विभाजनास समर्थन देत नाही."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"हे अॅप फक्त एका विंडोमध्ये उघडले जाऊ शकते."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"दुसऱ्या डिस्प्लेवर अॅप कदाचित चालणार नाही."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"दुसऱ्या डिस्प्लेवर अॅप लाँच होणार नाही."</string>
<string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रीन विभाजक"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 20f8ff6..be1dc24 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Tunjukkan"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Apl mungkin tidak berfungsi dengan skrin pisah."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Apl tidak menyokong skrin pisah."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Apl ini hanya boleh dibuka dalam 1 tetingkap."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Apl mungkin tidak berfungsi pada paparan kedua."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Apl tidak menyokong pelancaran pada paparan kedua."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pembahagi skrin pisah"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index dd2750b..3e18b49 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Avslutt oppbevaring"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Det kan hende at appen ikke fungerer med delt skjerm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen støtter ikke delt skjerm."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne appen kan bare åpnes i ett vindu."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer kanskje ikke på en sekundær skjerm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke kjøres på sekundære skjermer."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skilleelement for delt skjerm"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 054f05f..b056483 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Niet meer verbergen"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"De app werkt mogelijk niet met gesplitst scherm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App biedt geen ondersteuning voor gesplitst scherm."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Deze app kan slechts in 1 venster worden geopend."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App werkt mogelijk niet op een secundair scherm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App kan niet op secundaire displays worden gestart."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Scheiding voor gesplitst scherm"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index d0971a3..5fd81f4 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ଦେଖାନ୍ତୁ"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରିନରେ ଆପ୍ କାମ କରିନପାରେ।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ଆପ୍ ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରୀନକୁ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ଏହି ଆପକୁ କେବଳ 1ଟି ୱିଣ୍ଡୋରେ ଖୋଲାଯାଇପାରିବ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍ କାମ ନକରିପାରେ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍ ଲଞ୍ଚ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରୀନ ବିଭାଜକ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 989f8fc..ada79d1 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ਅਣਸਟੈਸ਼"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨੂੰ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ।"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ਇਹ ਐਪ ਸਿਰਫ਼ 1 ਵਿੰਡੋ ਵਿੱਚ ਖੋਲ੍ਹੀ ਜਾ ਸਕਦੀ ਹੈ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇ \'ਤੇ ਕੰਮ ਨਾ ਕਰੇ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇਆਂ \'ਤੇ ਲਾਂਚ ਕਰਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
<string name="accessibility_divider" msgid="703810061635792791">"ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਡਿਵਾਈਡਰ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 3458f26..a97fd5c 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zabierz ze schowka"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacja może nie działać przy podzielonym ekranie."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacja nie obsługuje dzielonego ekranu."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ta aplikacja może być otwarta tylko w 1 oknie."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacja może nie działać na dodatkowym ekranie."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacja nie obsługuje uruchamiania na dodatkowych ekranach."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Linia dzielenia ekranu"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index aeb7f9a..e0636d4 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Remover do armazenamento"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"A app pode não funcionar com o ecrã dividido."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A app não é compatível com o ecrã dividido."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app só pode ser aberta em 1 janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"A app pode não funcionar num ecrã secundário."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A app não é compatível com o início em ecrãs secundários."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor do ecrã dividido"</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 23b2099..9227216 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulează stocarea"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Este posibil ca aplicația să nu funcționeze cu ecranul împărțit."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplicația nu acceptă ecranul împărțit."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplicația poate fi deschisă într-o singură fereastră."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplicația nu acceptă lansare pe ecrane secundare."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Separator pentru ecranul împărțit"</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index c1dfb19..f5fa1a4 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показать"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"В режиме разделения экрана приложение может работать нестабильно."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложение не поддерживает разделение экрана."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Это приложение можно открыть только в одном окне."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Приложение может не работать на дополнительном экране"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложение не поддерживает запуск на дополнительных экранах"</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделитель экрана"</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 5e51003..53e52f2 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"සඟවා තැබීම ඉවත් කරන්න"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"යෙදුම බෙදුම් තිරය සමග ක්රියා නොකළ හැකිය"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"යෙදුම බෙදුණු-තිරය සඳහා සහාය නොදක්වයි."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"මෙම යෙදුම විවෘත කළ හැක්කේ 1 කවුළුවක පමණයි."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"යෙදුම ද්විතියික සංදර්ශකයක ක්රියා නොකළ හැකිය."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"යෙදුම ද්විතීයික සංදර්ශක මත දියත් කිරීම සඳහා සහාය නොදක්වයි."</string>
<string name="accessibility_divider" msgid="703810061635792791">"බෙදුම්-තිර වෙන්කරණය"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index baf0324..f004fd4 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušiť skrytie"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikácia nemusí fungovať s rozdelenou obrazovkou."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikácia nepodporuje rozdelenú obrazovku."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Táto aplikácia môže byť otvorená iba v jednom okne."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikácia nemusí fungovať na sekundárnej obrazovke."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikácia nepodporuje spúšťanie na sekundárnych obrazovkách."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Rozdeľovač obrazovky"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 2a12a64..c4808059 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Razkrij"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija morda ne deluje v načinu razdeljenega zaslona."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podpira načina razdeljenega zaslona."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"To aplikacijo je mogoče odpreti samo v enem oknu."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija morda ne bo delovala na sekundarnem zaslonu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podpira zagona na sekundarnih zaslonih."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdelilnik zaslonov"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index eb0199f..b59d4db 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Mos e fshih"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacioni mund të mos funksionojë me ekranin e ndarë."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacioni nuk mbështet ekranin e ndarë."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ky aplikacion mund të hapet vetëm në 1 dritare."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacioni mund të mos funksionojë në një ekran dytësor."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacioni nuk mbështet nisjen në ekrane dytësore."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ndarësi i ekranit të ndarë"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 6e6019c..78d74d74 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Уклоните из тајне меморије"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Апликација можда неће радити са подељеним екраном."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликација не подржава подељени екран."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ова апликација може да се отвори само у једном прозору."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликација можда неће функционисати на секундарном екрану."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликација не подржава покретање на секундарним екранима."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделник подељеног екрана"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 419c31d..cda3040 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Återställ stash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Appen kanske inte fungerar med delad skärm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen har inte stöd för delad skärm."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denna app kan bara vara öppen i ett fönster."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen kanske inte fungerar på en sekundär skärm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan inte köras på en sekundär skärm."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Avdelare för delad skärm"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 0c9b96d..f0c8be5 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్స్టాచ్"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"స్క్రీన్ విభజనతో యాప్ పని చేయకపోవచ్చు."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"యాప్లో స్క్రీన్ విభజనకు మద్దతు లేదు."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ఈ యాప్ను 1 విండోలో మాత్రమే తెరవవచ్చు."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
<string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 94abe5b..2437e03 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"เอาออกจากที่เก็บส่วนตัว"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"แอปอาจใช้ไม่ได้กับโหมดแบ่งหน้าจอ"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"แอปไม่สนับสนุนการแยกหน้าจอ"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"แอปนี้เปิดได้ใน 1 หน้าต่างเท่านั้น"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"แอปอาจไม่ทำงานในจอแสดงผลรอง"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"แอปไม่รองรับการเรียกใช้ในจอแสดงผลรอง"</string>
<string name="accessibility_divider" msgid="703810061635792791">"เส้นแบ่งหน้าจอ"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 589bedd..86ef757 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"I-unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Posibleng hindi gumana ang app sa split screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Hindi sinusuportahan ng app ang split-screen."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Sa 1 window lang puwedeng buksan ang app na ito."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Maaaring hindi gumana ang app sa pangalawang display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Hindi sinusuportahan ng app ang paglulunsad sa mga pangalawang display."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divider ng split-screen"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 6b73f2f..c4060cc 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Depolama"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Uygulama bölünmüş ekranda çalışmayabilir."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uygulama bölünmüş ekranı desteklemiyor."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu uygulama yalnızca 1 pencerede açılabilir."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uygulama ikincil ekranda çalışmayabilir."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uygulama ikincil ekranlarda başlatılmayı desteklemiyor."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcı"</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 50908cc..ca6a937 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے۔"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ایپ سپلٹ اسکرین کو سپورٹ نہیں کرتی۔"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"یہ ایپ صرف 1 ونڈو میں کھولی جا سکتی ہے۔"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن ہے ایپ ثانوی ڈسپلے پر کام نہ کرے۔"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ایپ ثانوی ڈسپلیز پر شروعات کا تعاون نہیں کرتی۔"</string>
<string name="accessibility_divider" msgid="703810061635792791">"سپلٹ اسکرین تقسیم کار"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index e0b802e..8f173d5 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Chiqarish"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Bu ilova ekranni ikkiga ajratish rejimini dastaklamaydi."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Bu ilova ekranni bo‘lish xususiyatini qo‘llab-quvvatlamaydi."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu ilovani faqat 1 ta oynada ochish mumkin."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Bu ilova qo‘shimcha ekranda ishlamasligi mumkin."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Bu ilova qo‘shimcha ekranlarda ishga tushmaydi."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ekranni ikkiga bo‘lish chizig‘i"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 71c3632..1d5b9d6 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Hiện"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Ứng dụng có thể không hoạt động với tính năng chia đôi màn hình."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Ứng dụng không hỗ trợ chia đôi màn hình."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ứng dụng này chỉ có thể mở 1 cửa sổ."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Ứng dụng có thể không hoạt động trên màn hình phụ."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Ứng dụng không hỗ trợ khởi chạy trên màn hình phụ."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bộ chia chia đôi màn hình"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 35ff2f2..87f2973 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消隐藏"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"应用可能无法在分屏模式下正常运行。"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"应用不支持分屏。"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此应用只能在 1 个窗口中打开。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"应用可能无法在辅显示屏上正常运行。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"应用不支持在辅显示屏上启动。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分屏分隔线"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 9c17f64..f9b22d22 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消保護"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"應用程式不支援分割畫面。"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此應用程式只可在 1 個視窗中開啟"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示屏上運作。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示屏上啟動。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index c4d9ca4..1438e52 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消暫時隱藏"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"這個應用程式不支援分割畫面。"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"這個應用程式只能在 1 個視窗中開啟。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示器上運作。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示器上啟動。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 4d8f7e3..e9238dc 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Susa isiteshi"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Izinhlelo zokusebenza kungenzeka zingasebenzi ngesikrini esihlukanisiwe."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uhlelo lokusebenza alusekeli isikrini esihlukanisiwe."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Le-app ingavulwa kuphela ewindini eli-1."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uhlelo lokusebenza kungenzeka lungasebenzi kusibonisi sesibili."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uhlelo lokusebenza alusekeli ukuqalisa kuzibonisi zesibili."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Isihlukanisi sokuhlukanisa isikrini"</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index bec6844..dd8afff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -475,8 +475,13 @@
@VisibleForTesting
public void onStatusBarStateChanged(boolean isShade) {
+ boolean didChange = mIsStatusBarShade != isShade;
+ if (DEBUG_BUBBLE_CONTROLLER) {
+ Log.d(TAG, "onStatusBarStateChanged isShade=" + isShade + " didChange=" + didChange);
+ }
mIsStatusBarShade = isShade;
- if (!mIsStatusBarShade) {
+ if (!mIsStatusBarShade && didChange) {
+ // Only collapse stack on change
collapseStack();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index ae49616..45b234a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -122,6 +122,7 @@
private int mDensity;
private final boolean mDimNonImeSide;
+ private ValueAnimator mDividerFlingAnimator;
public SplitLayout(String windowName, Context context, Configuration configuration,
SplitLayoutHandler splitLayoutHandler,
@@ -395,6 +396,10 @@
mSplitWindowManager.release(t);
mDisplayImeController.removePositionProcessor(mImePositionProcessor);
mImePositionProcessor.reset();
+ if (mDividerFlingAnimator != null) {
+ mDividerFlingAnimator.cancel();
+ }
+ resetDividerPosition();
}
public void release() {
@@ -577,13 +582,18 @@
CUJ_SPLIT_SCREEN_RESIZE);
return;
}
- ValueAnimator animator = ValueAnimator
+
+ if (mDividerFlingAnimator != null) {
+ mDividerFlingAnimator.cancel();
+ }
+
+ mDividerFlingAnimator = ValueAnimator
.ofInt(from, to)
.setDuration(duration);
- animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- animator.addUpdateListener(
+ mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mDividerFlingAnimator.addUpdateListener(
animation -> updateDivideBounds((int) animation.getAnimatedValue()));
- animator.addListener(new AnimatorListenerAdapter() {
+ mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (flingFinishedCallback != null) {
@@ -591,14 +601,15 @@
}
InteractionJankMonitorUtils.endTracing(
CUJ_SPLIT_SCREEN_RESIZE);
+ mDividerFlingAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
- setDividePosition(to, true /* applyLayoutChange */);
+ mDividerFlingAnimator = null;
}
});
- animator.start();
+ mDividerFlingAnimator.start();
}
/** Switch both surface position with animation. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 90b35a5..44bcdb2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -82,7 +82,7 @@
mTasks.put(taskInfo.taskId, state);
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
+ mWindowDecorationViewModel.onTaskOpening(taskInfo, leash, t, t);
t.apply();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 168f6d7..6e710f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -120,7 +120,7 @@
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- mWindowDecorViewModel.createWindowDecoration(
+ mWindowDecorViewModel.onTaskOpening(
change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@@ -128,31 +128,23 @@
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- mWindowDecorViewModel.setupWindowDecorationForTransition(
- change.getTaskInfo(), startT, finishT);
+ mWindowDecorViewModel.onTaskClosing(change.getTaskInfo(), startT, finishT);
}
private void onChangeTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- mWindowDecorViewModel.setupWindowDecorationForTransition(
- change.getTaskInfo(), startT, finishT);
+ mWindowDecorViewModel.onTaskChanging(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
private void onToFrontTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- boolean exists = mWindowDecorViewModel.setupWindowDecorationForTransition(
- change.getTaskInfo(),
- startT,
- finishT);
- if (!exists) {
- // Window caption does not exist, create it
- mWindowDecorViewModel.createWindowDecoration(
- change.getTaskInfo(), change.getLeash(), startT, finishT);
- }
+ mWindowDecorViewModel.onTaskChanging(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@Override
@@ -188,4 +180,4 @@
mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
}
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 75a4091..6623f5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -103,7 +103,7 @@
if (mWindowDecorViewModelOptional.isPresent()) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
createdWindowDecor = mWindowDecorViewModelOptional.get()
- .createWindowDecoration(taskInfo, leash, t, t);
+ .onTaskOpening(taskInfo, leash, t, t);
t.apply();
}
if (!createdWindowDecor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 17d7f5d..5376ae3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -97,6 +97,8 @@
private int mShelfHeight;
/** Whether the user has resized the PIP manually. */
private boolean mHasUserResizedPip;
+ /** Whether the user has moved the PIP manually. */
+ private boolean mHasUserMovedPip;
/**
* Areas defined by currently visible apps that they prefer to keep clear from overlays such as
* the PiP. Restricted areas may only move the PiP a limited amount from its anchor position.
@@ -279,6 +281,7 @@
if (changed) {
clearReentryState();
setHasUserResizedPip(false);
+ setHasUserMovedPip(false);
}
}
@@ -442,6 +445,16 @@
mHasUserResizedPip = hasUserResizedPip;
}
+ /** Returns whether the user has moved the PIP. */
+ public boolean hasUserMovedPip() {
+ return mHasUserMovedPip;
+ }
+
+ /** Set whether the user has moved the PIP. */
+ public void setHasUserMovedPip(boolean hasUserMovedPip) {
+ mHasUserMovedPip = hasUserMovedPip;
+ }
+
/**
* Registers a callback when the minimal size of PIP that is set by the app changes.
*/
@@ -577,6 +590,8 @@
pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
+ pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip);
+ pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip);
if (mPipReentryState == null) {
pw.println(innerPrefix + "mPipReentryState=null");
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
index 84071e0..690505e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.SystemProperties;
import android.util.ArraySet;
import android.view.Gravity;
@@ -34,6 +35,10 @@
*/
public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm {
+ private boolean mKeepClearAreaGravityEnabled =
+ SystemProperties.getBoolean(
+ "persist.wm.debug.enable_pip_keep_clear_algorithm_gravity", false);
+
protected int mKeepClearAreasPadding;
public PhonePipKeepClearAlgorithm(Context context) {
@@ -53,31 +58,36 @@
Rect startingBounds = pipBoundsState.getBounds().isEmpty()
? pipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas()
: pipBoundsState.getBounds();
- float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
- int verticalGravity = Gravity.BOTTOM;
- int horizontalGravity;
- if (snapFraction >= 0.5f && snapFraction < 2.5f) {
- horizontalGravity = Gravity.RIGHT;
- } else {
- horizontalGravity = Gravity.LEFT;
- }
- // push the bounds based on the gravity
Rect insets = new Rect();
pipBoundsAlgorithm.getInsetBounds(insets);
if (pipBoundsState.isImeShowing()) {
insets.bottom -= pipBoundsState.getImeHeight();
}
- Rect pushedBounds = new Rect(startingBounds);
- if (verticalGravity == Gravity.BOTTOM) {
- pushedBounds.offsetTo(pushedBounds.left,
- insets.bottom - pushedBounds.height());
+ Rect pipBounds = new Rect(startingBounds);
+
+ // move PiP towards corner if user hasn't moved it manually or the flag is on
+ if (mKeepClearAreaGravityEnabled
+ || (!pipBoundsState.hasUserMovedPip() && !pipBoundsState.hasUserResizedPip())) {
+ float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
+ int verticalGravity = Gravity.BOTTOM;
+ int horizontalGravity;
+ if (snapFraction >= 0.5f && snapFraction < 2.5f) {
+ horizontalGravity = Gravity.RIGHT;
+ } else {
+ horizontalGravity = Gravity.LEFT;
+ }
+ if (verticalGravity == Gravity.BOTTOM) {
+ pipBounds.offsetTo(pipBounds.left,
+ insets.bottom - pipBounds.height());
+ }
+ if (horizontalGravity == Gravity.RIGHT) {
+ pipBounds.offsetTo(insets.right - pipBounds.width(), pipBounds.top);
+ } else {
+ pipBounds.offsetTo(insets.left, pipBounds.top);
+ }
}
- if (horizontalGravity == Gravity.RIGHT) {
- pushedBounds.offsetTo(insets.right - pushedBounds.width(), pushedBounds.top);
- } else {
- pushedBounds.offsetTo(insets.left, pushedBounds.top);
- }
- return findUnoccludedPosition(pushedBounds, pipBoundsState.getRestrictedKeepClearAreas(),
+
+ return findUnoccludedPosition(pipBounds, pipBoundsState.getRestrictedKeepClearAreas(),
pipBoundsState.getUnrestrictedKeepClearAreas(), insets);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index d28a9f3..efe938f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -612,12 +612,21 @@
new DisplayInsetsController.OnInsetsChangedListener() {
@Override
public void insetsChanged(InsetsState insetsState) {
+ DisplayLayout pendingLayout =
+ mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId());
+ if (mIsInFixedRotation
+ || pendingLayout.rotation()
+ != mPipBoundsState.getDisplayLayout().rotation()) {
+ // bail out if there is a pending rotation or fixed rotation change
+ return;
+ }
int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
onDisplayChanged(
mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
false /* saveRestoreSnapFraction */);
int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
if (!mEnablePipKeepClearAlgorithm) {
+ // offset PiP to adjust for bottom inset change
int pipTop = mPipBoundsState.getBounds().top;
int diff = newMaxMovementBound - oldMaxMovementBound;
if (diff < 0 && pipTop > newMaxMovementBound) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index a9a97be..83bc7c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -875,6 +875,8 @@
}
if (touchState.isDragging()) {
+ mPipBoundsState.setHasUserMovedPip(true);
+
// Move the pinned stack freely
final PointF lastDelta = touchState.getLastTouchDelta();
float lastX = mStartPosition.x + mDelta.x;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 94ca9d3..4cb7623 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1040,7 +1040,7 @@
mIsDividerRemoteAnimating = false;
mSplitLayout.getInvisibleBounds(mTempRect1);
- if (childrenToTop == null) {
+ if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) {
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
@@ -1299,13 +1299,6 @@
}
}
- private void onStageChildTaskEnterPip() {
- // When the exit split-screen is caused by one of the task enters auto pip,
- // we want both tasks to be put to bottom instead of top, otherwise it will end up
- // a fullscreen plus a pinned task instead of pinned only at the end of the transition.
- exitSplitScreen(null, EXIT_REASON_CHILD_TASK_ENTER_PIP);
- }
-
private void updateRecentTasksSplitPair() {
if (!mShouldUpdateRecents) {
return;
@@ -2068,7 +2061,6 @@
// Update divider state after animation so that it is still around and positioned
// properly for the animation itself.
mSplitLayout.release();
- mSplitLayout.resetDividerPosition();
mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
}
}
@@ -2345,11 +2337,6 @@
}
@Override
- public void onChildTaskEnterPip() {
- StageCoordinator.this.onStageChildTaskEnterPip();
- }
-
- @Override
public void onRootTaskVanished() {
reset();
StageCoordinator.this.onRootTaskVanished();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index bcf900b..358f712 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -18,7 +18,6 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -74,8 +73,6 @@
void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
- void onChildTaskEnterPip();
-
void onRootTaskVanished();
void onNoLongerSupportMultiWindow();
@@ -257,9 +254,6 @@
// Status is managed/synchronized by the transition lifecycle.
return;
}
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
- mCallbacks.onChildTaskEnterPip();
- }
sendStatusChanged();
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 8369569..e40db4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -55,6 +55,8 @@
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;
+import java.util.function.Supplier;
+
/**
* View model for the window decoration with a caption and shadows. Works with
* {@link CaptionWindowDecoration}.
@@ -62,6 +64,8 @@
public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private static final String TAG = "CaptionViewModel";
+ private final CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
+ private final Supplier<InputManager> mInputManagerSupplier;
private final ActivityTaskManager mActivityTaskManager;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -77,6 +81,7 @@
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
+ private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
public CaptionWindowDecorViewModel(
Context context,
@@ -86,6 +91,29 @@
DisplayController displayController,
SyncTransactionQueue syncQueue,
DesktopModeController desktopModeController) {
+ this(
+ context,
+ mainHandler,
+ mainChoreographer,
+ taskOrganizer,
+ displayController,
+ syncQueue,
+ desktopModeController,
+ new CaptionWindowDecoration.Factory(),
+ InputManager::getInstance);
+ }
+
+ public CaptionWindowDecorViewModel(
+ Context context,
+ Handler mainHandler,
+ Choreographer mainChoreographer,
+ ShellTaskOrganizer taskOrganizer,
+ DisplayController displayController,
+ SyncTransactionQueue syncQueue,
+ DesktopModeController desktopModeController,
+ CaptionWindowDecoration.Factory captionWindowDecorFactory,
+ Supplier<InputManager> inputManagerSupplier) {
+
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
@@ -94,7 +122,13 @@
mDisplayController = displayController;
mSyncQueue = syncQueue;
mDesktopModeController = desktopModeController;
- mTransitionDragActive = false;
+
+ mCaptionWindowDecorFactory = captionWindowDecorFactory;
+ mInputManagerSupplier = inputManagerSupplier;
+ }
+
+ void setEventReceiverFactory(EventReceiverFactory eventReceiverFactory) {
+ mEventReceiverFactory = eventReceiverFactory;
}
@Override
@@ -103,42 +137,13 @@
}
@Override
- public boolean createWindowDecoration(
+ public boolean onTaskOpening(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
if (!shouldShowWindowDecor(taskInfo)) return false;
- CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
- if (oldDecoration != null) {
- // close the old decoration if it exists to avoid two window decorations being added
- oldDecoration.close();
- }
- final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- taskSurface,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
- mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
-
- TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration,
- mDragStartListener);
- CaptionTouchEventListener touchEventListener =
- new CaptionTouchEventListener(taskInfo, taskPositioner,
- windowDecoration.getDragDetector());
- windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
- windowDecoration.setDragResizeCallback(taskPositioner);
- setupWindowDecorationForTransition(taskInfo, startT, finishT);
- if (mInputMonitor == null) {
- mInputMonitor = InputManager.getInstance().monitorGestureInput(
- "caption-touch", mContext.getDisplayId());
- mEventReceiver = new EventReceiver(
- mInputMonitor.getInputChannel(), Looper.myLooper());
- }
+ createWindowDecoration(taskInfo, taskSurface, startT, finishT);
return true;
}
@@ -151,25 +156,45 @@
}
@Override
- public boolean setupWindowDecorationForTransition(
+ public void onTaskChanging(
+ RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
+ if (!shouldShowWindowDecor(taskInfo)) {
+ if (decoration != null) {
+ destroyWindowDecoration(taskInfo);
+ }
+ return;
+ }
+
+ if (decoration == null) {
+ createWindowDecoration(taskInfo, taskSurface, startT, finishT);
+ } else {
+ decoration.relayout(taskInfo, startT, finishT);
+ }
+ }
+
+ @Override
+ public void onTaskClosing(
RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
- if (decoration == null) return false;
+ if (decoration == null) return;
decoration.relayout(taskInfo, startT, finishT);
- return true;
}
@Override
- public boolean destroyWindowDecoration(RunningTaskInfo taskInfo) {
+ public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
final CaptionWindowDecoration decoration =
mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
- if (decoration == null) return false;
+ if (decoration == null) return;
decoration.close();
- return true;
}
private class CaptionTouchEventListener implements
@@ -217,6 +242,7 @@
decoration.setButtonVisibility();
}
}
+
private void injectBackKey() {
sendBackEvent(KeyEvent.ACTION_DOWN);
sendBackEvent(KeyEvent.ACTION_UP);
@@ -266,7 +292,7 @@
*/
private void handleEventForMove(MotionEvent e) {
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- int windowingMode = mDesktopModeController
+ int windowingMode = mDesktopModeController
.getDisplayAreaWindowingMode(taskInfo.displayId);
if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
return;
@@ -302,7 +328,7 @@
}
// InputEventReceiver to listen for touch input outside of caption bounds
- private class EventReceiver extends InputEventReceiver {
+ class EventReceiver extends InputEventReceiver {
EventReceiver(InputChannel channel, Looper looper) {
super(channel, looper);
}
@@ -318,8 +344,15 @@
}
}
+ class EventReceiverFactory {
+ EventReceiver create(InputChannel channel, Looper looper) {
+ return new EventReceiver(channel, looper);
+ }
+ }
+
/**
* Handle MotionEvents relevant to focused task's caption that don't directly touch it
+ *
* @param ev the {@link MotionEvent} received by {@link EventReceiver}
*/
private void handleReceivedMotionEvent(MotionEvent ev) {
@@ -401,7 +434,6 @@
return focusedDecor;
}
-
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
return DesktopModeStatus.IS_SUPPORTED
@@ -410,7 +442,47 @@
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
- private class DragStartListenerImpl implements TaskPositioner.DragStartListener{
+ private void createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (oldDecoration != null) {
+ // close the old decoration if it exists to avoid two window decorations being added
+ oldDecoration.close();
+ }
+ final CaptionWindowDecoration windowDecoration =
+ mCaptionWindowDecorFactory.create(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ taskSurface,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+ mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+
+ TaskPositioner taskPositioner =
+ new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
+ CaptionTouchEventListener touchEventListener =
+ new CaptionTouchEventListener(
+ taskInfo, taskPositioner, windowDecoration.getDragDetector());
+ windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
+ windowDecoration.setDragResizeCallback(taskPositioner);
+ windowDecoration.relayout(taskInfo, startT, finishT);
+ if (mInputMonitor == null) {
+ InputManager inputManager = mInputManagerSupplier.get();
+ mInputMonitor =
+ inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
+ mEventReceiver =
+ mEventReceiverFactory.create(
+ mInputMonitor.getInputChannel(), Looper.myLooper());
+ }
+ }
+
+ private class DragStartListenerImpl implements TaskPositioner.DragStartListener {
@Override
public void onDragStart(int taskId) {
mWindowDecorByTaskId.get(taskId).closeHandleMenu();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 59576cd..037ca20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -42,7 +42,8 @@
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
- * {@link CaptionWindowDecorViewModel}. The caption bar contains a handle, back button, and close button.
+ * {@link CaptionWindowDecorViewModel}. The caption bar contains a handle, back button, and close
+ * button.
*
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
*/
@@ -181,12 +182,12 @@
if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
closeDragResizeListener();
mDragResizeListener = new DragResizeInputListener(
- mContext,
- mHandler,
- mChoreographer,
- mDisplay.getDisplayId(),
- mDecorationContainerSurface,
- mDragResizeCallback);
+ mContext,
+ mHandler,
+ mChoreographer,
+ mDisplay.getDisplayId(),
+ mDecorationContainerSurface,
+ mDragResizeCallback);
}
int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
@@ -242,7 +243,6 @@
/**
* Sets the visibility of buttons and color of caption based on desktop mode status
- *
*/
void setButtonVisibility() {
mDesktopActive = DesktopModeStatus.isActive(mContext);
@@ -313,6 +313,7 @@
/**
* Close an open handle menu if input is outside of menu coordinates
+ *
* @param ev the tapped point to compare against
*/
void closeHandleMenuIfNeeded(MotionEvent ev) {
@@ -329,6 +330,7 @@
/**
* Offset the coordinates of a {@link MotionEvent} to be in the same coordinate space as caption
+ *
* @param ev the {@link MotionEvent} to offset
* @return the point of the input in local space
*/
@@ -343,7 +345,8 @@
/**
* Determine if a passed MotionEvent is in a view in caption
- * @param ev the {@link MotionEvent} to check
+ *
+ * @param ev the {@link MotionEvent} to check
* @param layoutId the id of the view
* @return {@code true} if event is inside the specified view, {@code false} if not
*/
@@ -363,6 +366,7 @@
* Check a passed MotionEvent if a click has occurred on any button on this caption
* Note this should only be called when a regular onClick is not possible
* (i.e. the button was clicked through status bar layer)
+ *
* @param ev the MotionEvent to compare
*/
void checkClickEvent(MotionEvent ev) {
@@ -399,4 +403,27 @@
closeHandleMenu();
super.close();
}
+
+ static class Factory {
+
+ CaptionWindowDecoration create(
+ Context context,
+ DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer,
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Handler handler,
+ Choreographer choreographer,
+ SyncTransactionQueue syncQueue) {
+ return new CaptionWindowDecoration(
+ context,
+ displayController,
+ taskOrganizer,
+ taskInfo,
+ taskSurface,
+ handler,
+ choreographer,
+ syncQueue);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 2ce4d04..907977c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -28,7 +28,6 @@
* servers.
*/
public interface WindowDecorViewModel {
-
/**
* Sets the transition starter that starts freeform task transitions.
*
@@ -37,16 +36,16 @@
void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter);
/**
- * Creates a window decoration for the given task.
- * Can be {@code null} for Fullscreen tasks but not Freeform ones.
+ * Creates a window decoration for the given task. Can be {@code null} for Fullscreen tasks but
+ * not Freeform ones.
*
- * @param taskInfo the initial task info of the task
+ * @param taskInfo the initial task info of the task
* @param taskSurface the surface of the task
- * @param startT the start transaction to be applied before the transition
- * @param finishT the finish transaction to restore states after the transition
+ * @param startT the start transaction to be applied before the transition
+ * @param finishT the finish transaction to restore states after the transition
* @return {@code true} if window decoration was created, {@code false} otherwise
*/
- boolean createWindowDecoration(
+ boolean onTaskOpening(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
@@ -54,7 +53,7 @@
/**
* Notifies a task info update on the given task, with the window decoration created previously
- * for this task by {@link #createWindowDecoration}.
+ * for this task by {@link #onTaskOpening}.
*
* @param taskInfo the new task info of the task
*/
@@ -62,13 +61,29 @@
/**
* Notifies a transition is about to start about the given task to give the window decoration a
- * chance to prepare for this transition.
+ * chance to prepare for this transition. Unlike {@link #onTaskInfoChanged}, this method creates
+ * a window decoration if one does not exist but is required.
*
- * @param startT the start transaction to be applied before the transition
- * @param finishT the finish transaction to restore states after the transition
- * @return {@code true} if window decoration exists, {@code false} otherwise
+ * @param taskInfo the initial task info of the task
+ * @param taskSurface the surface of the task
+ * @param startT the start transaction to be applied before the transition
+ * @param finishT the finish transaction to restore states after the transition
*/
- boolean setupWindowDecorationForTransition(
+ void onTaskChanging(
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT);
+
+ /**
+ * Notifies that the given task is about to close to give the window decoration a chance to
+ * prepare for this transition.
+ *
+ * @param taskInfo the initial task info of the task
+ * @param startT the start transaction to be applied before the transition
+ * @param finishT the finish transaction to restore states after the transition
+ */
+ void onTaskClosing(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT);
@@ -77,7 +92,6 @@
* Destroys the window decoration of the give task.
*
* @param taskInfo the info of the task
- * @return {@code true} if window decoration was destroyed, {@code false} otherwise
*/
- boolean destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
-}
+ void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 7ecb3f3..9215496 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -251,7 +251,9 @@
}
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
- final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId);
+ final int captionWidth = params.mCaptionWidthId == Resources.ID_NULL
+ ? taskBounds.width()
+ : loadDimensionPixelSize(resources, params.mCaptionWidthId);
startT.setPosition(
mCaptionContainerSurface,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 7068a84..48415d4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -103,7 +103,7 @@
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mWindowDecorViewModel).createWindowDecoration(
+ verify(mWindowDecorViewModel).onTaskOpening(
change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@@ -120,7 +120,7 @@
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mWindowDecorViewModel).setupWindowDecorationForTransition(
+ verify(mWindowDecorViewModel).onTaskClosing(
change.getTaskInfo(), startT, finishT);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
new file mode 100644
index 0000000..8b134ed
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.InputChannel;
+import android.view.InputMonitor;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.GrantPermissionRule;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/** Tests of {@link CaptionWindowDecorViewModel} */
+@SmallTest
+public class CaptionWindowDecorViewModelTests extends ShellTestCase {
+ @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
+
+ @Mock private CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
+
+ @Mock private Handler mMainHandler;
+
+ @Mock private Choreographer mMainChoreographer;
+
+ @Mock private ShellTaskOrganizer mTaskOrganizer;
+
+ @Mock private DisplayController mDisplayController;
+
+ @Mock private SyncTransactionQueue mSyncQueue;
+
+ @Mock private DesktopModeController mDesktopModeController;
+
+ @Mock private InputMonitor mInputMonitor;
+
+ @Mock private InputChannel mInputChannel;
+
+ @Mock private CaptionWindowDecorViewModel.EventReceiverFactory mEventReceiverFactory;
+
+ @Mock private CaptionWindowDecorViewModel.EventReceiver mEventReceiver;
+
+ @Mock private InputManager mInputManager;
+
+ private final List<InputManager> mMockInputManagers = new ArrayList<>();
+
+ private CaptionWindowDecorViewModel mCaptionWindowDecorViewModel;
+
+ @Before
+ public void setUp() {
+ mMockInputManagers.add(mInputManager);
+
+ mCaptionWindowDecorViewModel =
+ new CaptionWindowDecorViewModel(
+ mContext,
+ mMainHandler,
+ mMainChoreographer,
+ mTaskOrganizer,
+ mDisplayController,
+ mSyncQueue,
+ mDesktopModeController,
+ mCaptionWindowDecorFactory,
+ new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
+ mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
+
+ doReturn(mCaptionWindowDecoration)
+ .when(mCaptionWindowDecorFactory)
+ .create(any(), any(), any(), any(), any(), any(), any(), any());
+
+ when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
+ when(mEventReceiverFactory.create(any(), any())).thenReturn(mEventReceiver);
+ when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
+ }
+
+ @Test
+ public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception {
+ Looper.prepare();
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, WINDOWING_MODE_FREEFORM);
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ GrantPermissionRule.grant(android.Manifest.permission.MONITOR_INPUT);
+
+ mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+ verify(mCaptionWindowDecorFactory)
+ .create(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ verify(mCaptionWindowDecoration).close();
+ }
+
+ @Test
+ public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception {
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, WINDOWING_MODE_UNDEFINED);
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+
+ verify(mCaptionWindowDecorFactory, never())
+ .create(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+
+ verify(mCaptionWindowDecorFactory)
+ .create(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+ }
+
+ private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
+ ActivityManager.RunningTaskInfo taskInfo =
+ new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setVisible(true)
+ .build();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ return taskInfo;
+ }
+
+ private static class MockObjectSupplier<T> implements Supplier<T> {
+ private final List<T> mObjects;
+ private final Supplier<T> mDefaultSupplier;
+ private int mNumOfCalls = 0;
+
+ private MockObjectSupplier(List<T> objects, Supplier<T> defaultSupplier) {
+ mObjects = objects;
+ mDefaultSupplier = defaultSupplier;
+ }
+
+ @Override
+ public T get() {
+ final T mock =
+ mNumOfCalls < mObjects.size() ? mObjects.get(mNumOfCalls)
+ : mDefaultSupplier.get();
+ ++mNumOfCalls;
+ return mock;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 15181b1..dd9ab98 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -113,6 +113,12 @@
mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
mMockSurfaceControlAddWindowT = createMockSurfaceControlTransaction();
+ mRelayoutParams.mLayoutResId = 0;
+ mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
+ // Caption should have fixed width except in testLayoutResultCalculation_fullWidthCaption()
+ mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
+ mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
+
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
.create(any(), any(), any());
}
@@ -435,6 +441,58 @@
assertThat(additionalWindow.mWindowSurface).isNull();
}
+ @Test
+ public void testLayoutResultCalculation_fullWidthCaption() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+ createMockSurfaceControlBuilder(taskBackgroundSurface);
+ mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+ final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder captionContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(captionContainerSurface);
+ mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mMockSurfaceControlTransactions.add(t);
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .build();
+ taskInfo.isFocused = true;
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+ mRelayoutParams.mCaptionWidthId = Resources.ID_NULL;
+ windowDecor.relayout(taskInfo);
+
+ verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
+ verify(captionContainerSurfaceBuilder).setContainerLayer();
+ verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
+ // Width of the captionContainerSurface should match the width of TASK_BOUNDS
+ verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
+ verify(mMockSurfaceControlStartT).show(captionContainerSurface);
+ }
+
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
@@ -490,11 +548,6 @@
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
- mRelayoutParams.mLayoutResId = 0;
- mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
- mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
- mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
-
relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index af05078..73bb5eb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -34,6 +34,8 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import java.util.Arrays;
@@ -96,6 +98,22 @@
mAbortInstall = true;
}
}
+
+ final String installerPackageNameFromIntent = getIntent().getStringExtra(
+ Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+ if (installerPackageNameFromIntent != null) {
+ final String callingPkgName = getLaunchedFromPackage();
+ if (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName)
+ && mPackageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES,
+ callingPkgName) != PackageManager.PERMISSION_GRANTED) {
+ Log.e(LOG_TAG, "The given installer package name " + installerPackageNameFromIntent
+ + " is invalid. Remove it.");
+ EventLog.writeEvent(0x534e4554, "236687884", getLaunchedFromUid(),
+ "Invalid EXTRA_INSTALLER_PACKAGE_NAME");
+ getIntent().removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+ }
+ }
+
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index f868c69..e66e3a1 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -278,7 +278,7 @@
<string name="mock_location_app_set" msgid="4706722469342913843">"Ứng dụng vị trí mô phỏng: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="debug_networking_category" msgid="6829757985772659599">"Mạng"</string>
<string name="wifi_display_certification" msgid="1805579519992520381">"Chứng nhận hiển thị không dây"</string>
- <string name="wifi_verbose_logging" msgid="1785910450009679371">"Bật ghi nhật ký chi tiết Wi‑Fi"</string>
+ <string name="wifi_verbose_logging" msgid="1785910450009679371">"Bật tính năng ghi nhật ký chi tiết Wi‑Fi"</string>
<string name="wifi_scan_throttling" msgid="2985624788509913617">"Hạn chế quét tìm Wi-Fi"</string>
<string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Tạo địa chỉ MAC ngẫu nhiên, không cố định mỗi khi kết nối Wi-Fi"</string>
<string name="mobile_data_always_on" msgid="8275958101875563572">"Dữ liệu di động luôn hoạt động"</string>
@@ -290,7 +290,7 @@
<string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Chọn phiên bản Bluetooth AVRCP"</string>
<string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Phiên bản Bluetooth MAP"</string>
<string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Chọn phiên bản Bluetooth MAP"</string>
- <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec âm thanh Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bộ mã hoá và giải mã âm thanh qua Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Kích hoạt chế độ chọn codec\nâm thanh Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Tốc độ lấy mẫu âm thanh Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Kích hoạt chế độ chọn codec\nâm thanh Bluetooth: Tần số lấy mẫu"</string>
@@ -384,7 +384,7 @@
<string name="window_blurs" msgid="6831008984828425106">"Cho phép làm mờ cửa sổ"</string>
<string name="force_msaa" msgid="4081288296137775550">"Bắt buộc 4x MSAA"</string>
<string name="force_msaa_summary" msgid="9070437493586769500">"Bật 4x MSAA trong ứng dụng OpenGL ES 2.0"</string>
- <string name="show_non_rect_clip" msgid="7499758654867881817">"Gỡ lỗi hoạt động của clip không phải là hình chữ nhật"</string>
+ <string name="show_non_rect_clip" msgid="7499758654867881817">"Gỡ lỗi hoạt động của đoạn không phải hình chữ nhật"</string>
<string name="track_frame_time" msgid="522674651937771106">"Kết xuất HWUI cấu hình"</string>
<string name="enable_gpu_debug_layers" msgid="4986675516188740397">"Bật lớp gỡ lỗi GPU"</string>
<string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"Cho phép tải lớp gỡ lỗi GPU cho ứng dụng gỡ lỗi"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 949bbfb..d1ac7d0 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1125,7 +1125,9 @@
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
<string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string>
<!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited -->
- <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging is paused</string>
+ <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging paused</string>
+ <!-- [CHAR_LIMIT=80] Label for battery charging future pause -->
+ <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging to <xliff:g id="dock_defender_threshold">%2$s</xliff:g></string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 5df79e1..e6ac48f 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -34,10 +34,7 @@
"res",
],
- static_libs: [
- "PluginCoreLib",
- "androidx.core_core-animation-nodeps",
- ],
+ static_libs: ["androidx.core_core-animation-nodeps"],
manifest: "AndroidManifest.xml",
kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index fdfad2b..54aa351 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -75,7 +75,7 @@
*/
interface Controller {
/** The [ViewRootImpl] of this controller. */
- val viewRoot: ViewRootImpl
+ val viewRoot: ViewRootImpl?
/**
* The identity object of the source animated by this controller. This animator will ensure
@@ -807,15 +807,17 @@
* inversely, removed from the overlay when the source is moved back to its original position).
*/
private fun synchronizeNextDraw(then: () -> Unit) {
- if (forceDisableSynchronization) {
- // Don't synchronize when inside an automated test.
+ val controllerRootView = controller.viewRoot?.view
+ if (forceDisableSynchronization || controllerRootView == null) {
+ // Don't synchronize when inside an automated test or if the controller root view is
+ // detached.
then()
return
}
- ViewRootSync.synchronizeNextDraw(controller.viewRoot.view, decorView, then)
+ ViewRootSync.synchronizeNextDraw(controllerRootView, decorView, then)
decorView.invalidate()
- controller.viewRoot.view.invalidate()
+ controllerRootView.invalidate()
}
private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index 43bfa74..0e2d23b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -195,8 +195,16 @@
val out = ArrayList<RemoteAnimationTarget>()
for (i in info.changes.indices) {
val change = info.changes[i]
- val changeIsWallpaper = change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0
- if (wallpapers != changeIsWallpaper) continue
+ if (change.hasFlags(TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ // For embedded container, when the parent Task is also in the transition, we
+ // should only animate the parent Task.
+ if (change.parent != null) continue
+ // For embedded container without parent, we should only animate if it fills
+ // the Task. Otherwise we may animate only partial of the Task.
+ if (!change.hasFlags(TransitionInfo.FLAG_FILLS_TASK)) continue
+ }
+ // Check if it is wallpaper
+ if (wallpapers != change.hasFlags(TransitionInfo.FLAG_IS_WALLPAPER)) continue
out.add(createTarget(change, info.changes.size - i, info, t))
if (leashMap != null) {
leashMap[change.leash] = out[out.size - 1].leash
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index ecee598..964ef8c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -28,7 +28,7 @@
private val source: View,
override val cuj: DialogCuj?,
) : DialogLaunchAnimator.Controller {
- override val viewRoot: ViewRootImpl
+ override val viewRoot: ViewRootImpl?
get() = source.viewRootImpl
override val sourceIdentity: Any = source
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
index 50c3d7e..d6db574 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
@@ -262,7 +262,7 @@
private fun dialogController(cuj: DialogCuj?): DialogLaunchAnimator.Controller {
return object : DialogLaunchAnimator.Controller {
- override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl
+ override val viewRoot: ViewRootImpl? = composeViewRoot.viewRootImpl
override val sourceIdentity: Any = this@ExpandableControllerImpl
override val cuj: DialogCuj? = cuj
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 8698844..e1f2174 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -20,7 +20,6 @@
import android.icu.text.NumberFormat
import android.util.TypedValue
import android.view.LayoutInflater
-import android.view.View
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import com.android.systemui.customization.R
@@ -152,15 +151,9 @@
view: AnimatableClockView,
) : DefaultClockFaceController(view) {
override fun recomputePadding(targetRegion: Rect?) {
- // We center the view within the targetRegion instead of within the parent
- // view by computing the difference and adding that to the padding.
- val parent = view.parent
- val yDiff =
- if (targetRegion != null && parent is View && parent.isLaidOut())
- targetRegion.centerY() - parent.height / 2f
- else 0f
+ // Ignore Target Region until top padding fixed in aod
val lp = view.getLayoutParams() as FrameLayout.LayoutParams
- lp.topMargin = (-0.5f * view.bottom + yDiff).toInt()
+ lp.topMargin = (-0.5f * view.bottom).toInt()
view.setLayoutParams(lp)
}
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 0abbb1e..b75c5c7 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -753,7 +753,7 @@
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 1282d77..f96644f 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -23,14 +23,11 @@
-keep class ** extends androidx.preference.PreferenceFragment
-keep class com.android.systemui.tuner.*
-# The plugins and animation subpackages both act as shared libraries that might be referenced in
+# The plugins subpackage acts as a shared library that might be referenced in
# dynamically-loaded plugin APKs.
-keep class com.android.systemui.plugins.** {
*;
}
--keep class !com.android.systemui.animation.R$**,com.android.systemui.animation.** {
- *;
-}
-keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
*;
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index b49afee..218c5cc 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -35,6 +35,7 @@
android:visibility="invisible" />
<FrameLayout
android:id="@+id/lockscreen_clock_view_large"
+ android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
diff --git a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
index 669f8fb..e5e17b7 100644
--- a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
@@ -17,4 +17,7 @@
<resources>
<dimen name="widget_big_font_size">54dp</dimen>
+
+ <!-- Margin above the ambient indication container -->
+ <dimen name="ambient_indication_container_margin_top">10dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index 579824a..57e5f8a 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -74,7 +74,7 @@
<string name="kg_password_pin_failed" msgid="5136259126330604009">"Bewerking met pincode voor simkaart is mislukt."</string>
<string name="kg_password_puk_failed" msgid="6778867411556937118">"Bewerking met pukcode voor simkaart is mislukt."</string>
<string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Invoermethode wijzigen"</string>
- <string name="airplane_mode" msgid="2528005343938497866">"Vliegtuigmodus"</string>
+ <string name="airplane_mode" msgid="2528005343938497866">"Vliegtuigmodus"</string>
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon vereist nadat het apparaat opnieuw is opgestart"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pincode vereist nadat het apparaat opnieuw is opgestart"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wachtwoord vereist nadat het apparaat opnieuw is opgestart"</string>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 3861d98..c5ffdc0 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -41,6 +41,9 @@
<!-- Minimum bottom margin under the security view -->
<dimen name="keyguard_security_view_bottom_margin">60dp</dimen>
+ <!-- Margin above the ambient indication container -->
+ <dimen name="ambient_indication_container_margin_top">0dp</dimen>
+
<dimen name="keyguard_eca_top_margin">18dp</dimen>
<dimen name="keyguard_eca_bottom_margin">12dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index a129fb6..da485a9 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -53,7 +53,7 @@
<string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
<!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. -->
- <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging is paused to protect battery</string>
+ <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging paused to protect battery</string>
<!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock. This is shown in small font at the bottom. -->
<string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string>
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
index 79ba7ead..aa655e6 100644
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
@@ -41,7 +41,7 @@
android:layout_width="@dimen/qs_media_app_icon_size"
android:layout_height="@dimen/qs_media_app_icon_size"
android:layout_marginStart="@dimen/qs_media_padding"
- android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginTop="@dimen/qs_media_rec_icon_top_margin"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
diff --git a/packages/SystemUI/res/values-h700dp/dimens.xml b/packages/SystemUI/res/values-h700dp/dimens.xml
new file mode 100644
index 0000000..055308f
--- /dev/null
+++ b/packages/SystemUI/res/values-h700dp/dimens.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <!-- Margin above the ambient indication container -->
+ <dimen name="ambient_indication_container_margin_top">15dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 8efd6f0..3a71994 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -17,4 +17,7 @@
<resources>
<!-- With the large clock, move up slightly from the center -->
<dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
+
+ <!-- Margin above the ambient indication container -->
+ <dimen name="ambient_indication_container_margin_top">20dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 868c003..3fc59e3 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -35,6 +35,11 @@
not appear immediately after user swipes to the side -->
<dimen name="qs_tiles_page_horizontal_margin">20dp</dimen>
+ <!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
+ <dimen name="qs_media_rec_icon_top_margin">27dp</dimen>
+ <dimen name="qs_media_rec_album_size">152dp</dimen>
+ <dimen name="qs_media_rec_album_side_margin">16dp</dimen>
+
<dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen>
<!-- Roughly the same distance as media on LS to media on QS. We will translate by this value
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 43f3c9e..bc88bee 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -133,6 +133,9 @@
<color name="biometric_dialog_accent">@color/material_dynamic_primary40</color>
<color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 -->
+ <!-- SFPS colors -->
+ <color name="sfps_chevron_fill">@color/material_dynamic_primary90</color>
+
<!-- UDFPS colors -->
<color name="udfps_enroll_icon">#699FF3</color>
<color name="udfps_moving_target_fill">#C2D7F7</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4f2ff22..e849b1f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1051,6 +1051,7 @@
<dimen name="qs_media_session_collapsed_guideline">144dp</dimen>
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
+ <dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
<dimen name="qs_media_rec_album_size">88dp</dimen>
<dimen name="qs_media_rec_album_side_margin">16dp</dimen>
<dimen name="qs_media_rec_album_bottom_margin">8dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e166bb9..b39f49f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -200,6 +200,8 @@
<!-- Informs the user that a screenshot is being saved. [CHAR LIMIT=50] -->
<string name="screenshot_saving_title">Saving screenshot\u2026</string>
+ <!-- Informs the user that a screenshot is being saved. [CHAR LIMIT=50] -->
+ <string name="screenshot_saving_work_profile_title">Saving screenshot to work profile\u2026</string>
<!-- Notification title displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=50] -->
<string name="screenshot_saved_title">Screenshot saved</string>
<!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index f6c75a2..c9ea794 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -39,6 +39,7 @@
private boolean mIsOrientationChanged;
private SplitBounds mSplitBounds;
private int mDesiredStagePosition;
+ private boolean mTaskbarInApp;
public Matrix getMatrix() {
return mMatrix;
@@ -57,6 +58,10 @@
mDesiredStagePosition = desiredStagePosition;
}
+ public void setTaskbarInApp(boolean taskbarInApp) {
+ mTaskbarInApp = taskbarInApp;
+ }
+
/**
* Updates the matrix based on the provided parameters
*/
@@ -83,8 +88,18 @@
? mSplitBounds.topTaskPercent
: (1 - (mSplitBounds.topTaskPercent + mSplitBounds.dividerHeightPercent));
// Scale portrait height to that of (actual screen - taskbar inset)
- fullscreenTaskHeight = (screenHeightPx - taskbarSize) * taskPercent;
- canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
+ fullscreenTaskHeight = (screenHeightPx) * taskPercent;
+ if (mTaskbarInApp) {
+ canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
+ } else {
+ if (mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+ // Top app isn't cropped at all by taskbar
+ canvasScreenRatio = 0;
+ } else {
+ // Same as fullscreen ratio
+ canvasScreenRatio = (float) canvasWidth / screenWidthPx;
+ }
+ }
} else {
// For landscape, scale the width
taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
index 3748eba..19d0a3d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
@@ -71,7 +71,7 @@
mKeyShadowInfo.offsetY,
mKeyShadowInfo.alpha
)
- val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DARKEN)
+ val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DST_ATOP)
renderNode.setRenderEffect(blend)
return renderNode
}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 458d22e..a25b281 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -25,7 +25,6 @@
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.widget.LockPatternUtils;
-import com.android.settingslib.Utils;
/**
* This class implements a smart emergency button that updates itself based
@@ -91,17 +90,6 @@
return super.onTouchEvent(event);
}
- /**
- * Reload colors from resources.
- **/
- public void reloadColors() {
- int color = Utils.getColorAttrDefaultColor(getContext(),
- com.android.internal.R.attr.textColorOnAccent);
- setTextColor(color);
- setBackground(getContext()
- .getDrawable(com.android.systemui.R.drawable.kg_emergency_button_background));
- }
-
@Override
public boolean performLongClick() {
return super.performLongClick();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 3e32cf5..860c8e3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -20,7 +20,6 @@
import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT;
-import android.annotation.CallSuper;
import android.content.res.ColorStateList;
import android.os.AsyncTask;
import android.os.CountDownTimer;
@@ -117,13 +116,6 @@
}
}
- @CallSuper
- @Override
- public void reloadColors() {
- super.reloadColors();
- mMessageAreaController.reloadColors();
- }
-
@Override
public boolean needsInput() {
return false;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index faaba63..2e9ad58 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -142,16 +141,6 @@
public void showMessage(CharSequence message, ColorStateList colorState) {
}
- /**
- * Reload colors from resources.
- **/
- @CallSuper
- public void reloadColors() {
- if (mEmergencyButton != null) {
- mEmergencyButton.reloadColors();
- }
- }
-
public void startAppearAnimation() {
if (TextUtils.isEmpty(mMessageAreaController.getMessage())) {
mMessageAreaController.setMessage(getInitialMessageResId());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 8197685..e6283b8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -26,7 +26,6 @@
val credentialAttempted: Boolean,
val deviceInteractive: Boolean,
val dreaming: Boolean,
- val encryptedOrLockdown: Boolean,
val fingerprintDisabled: Boolean,
val fingerprintLockedOut: Boolean,
val goingToSleep: Boolean,
@@ -37,6 +36,7 @@
val primaryUser: Boolean,
val shouldListenSfpsState: Boolean,
val shouldListenForFingerprintAssistant: Boolean,
+ val strongerAuthRequired: Boolean,
val switchingUser: Boolean,
val udfps: Boolean,
val userDoesNotHaveTrust: Boolean
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index c29f632..6a92162 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -116,13 +116,6 @@
return mView.getText();
}
- /**
- * Reload colors from resources.
- **/
- public void reloadColors() {
- mView.reloadColor();
- }
-
/** Factory for creating {@link com.android.keyguard.KeyguardMessageAreaController}. */
public static class Factory {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 0025986..195e8f9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.os.UserHandle;
import android.text.Editable;
@@ -39,7 +38,6 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
@@ -95,18 +93,6 @@
}
};
- @Override
- public void reloadColors() {
- super.reloadColors();
- int textColor = Utils.getColorAttr(mView.getContext(),
- android.R.attr.textColorPrimary).getDefaultColor();
- mPasswordEntry.setTextColor(textColor);
- mPasswordEntry.setHighlightColor(textColor);
- mPasswordEntry.setBackgroundTintList(ColorStateList.valueOf(textColor));
- mPasswordEntry.setForegroundTintList(ColorStateList.valueOf(textColor));
- mSwitchImeButton.setImageTintList(ColorStateList.valueOf(textColor));
- }
-
protected KeyguardPasswordViewController(KeyguardPasswordView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
SecurityMode securityMode,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index cdbfb24..571d274 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -35,7 +35,6 @@
import com.android.internal.widget.LockscreenCredential;
import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingClassifier;
import com.android.systemui.classifier.FalsingCollector;
@@ -272,16 +271,6 @@
}
@Override
- public void reloadColors() {
- super.reloadColors();
- mMessageAreaController.reloadColors();
- int textColor = Utils.getColorAttr(mLockPatternView.getContext(),
- android.R.attr.textColorSecondary).getDefaultColor();
- int errorColor = Utils.getColorError(mLockPatternView.getContext()).getDefaultColor();
- mLockPatternView.setColors(textColor, textColor, errorColor);
- }
-
- @Override
public void onPause() {
super.onPause();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 7876f07..f51ac32 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -70,12 +70,6 @@
}
@Override
- public void reloadColors() {
- super.reloadColors();
- mView.reloadColors();
- }
-
- @Override
public boolean startDisappearAnimation(Runnable finishRunnable) {
return mView.startDisappearAnimation(
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 01be33e..a72a484 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -363,16 +363,18 @@
final boolean sfpsEnabled = getResources().getBoolean(
R.bool.config_show_sidefps_hint_on_bouncer);
final boolean fpsDetectionRunning = mUpdateMonitor.isFingerprintDetectionRunning();
- final boolean needsStrongAuth = mUpdateMonitor.userNeedsStrongAuth();
+ final boolean isUnlockingWithFpAllowed =
+ mUpdateMonitor.isUnlockingWithFingerprintAllowed();
- boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning && !needsStrongAuth;
+ boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning
+ && isUnlockingWithFpAllowed;
if (DEBUG) {
Log.d(TAG, "sideFpsToShow=" + toShow + ", "
+ "mBouncerVisible=" + mBouncerVisible + ", "
+ "configEnabled=" + sfpsEnabled + ", "
+ "fpsDetectionRunning=" + fpsDetectionRunning + ", "
- + "needsStrongAuth=" + needsStrongAuth);
+ + "isUnlockingWithFpAllowed=" + isUnlockingWithFpAllowed);
}
if (toShow) {
mSideFpsController.get().show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
@@ -728,16 +730,20 @@
}
private void reloadColors() {
- mSecurityViewFlipperController.reloadColors();
+ resetViewFlipper();
mView.reloadColors();
}
/** Handles density or font scale changes. */
private void onDensityOrFontScaleChanged() {
- mSecurityViewFlipperController.onDensityOrFontScaleChanged();
+ resetViewFlipper();
+ mView.onDensityOrFontScaleChanged();
+ }
+
+ private void resetViewFlipper() {
+ mSecurityViewFlipperController.clearViews();
mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
mKeyguardSecurityCallback);
- mView.onDensityOrFontScaleChanged();
}
static class Factory {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 25afe11..a5c8c78 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -74,17 +74,8 @@
}
}
- /**
- * Reload colors of ui elements upon theme change.
- */
- public void reloadColors() {
- for (KeyguardInputViewController<KeyguardInputView> child : mChildren) {
- child.reloadColors();
- }
- }
-
/** Handles density or font scale changes. */
- public void onDensityOrFontScaleChanged() {
+ public void clearViews() {
mView.removeAllViews();
mChildren.clear();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 76f7d78..db44473 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -133,12 +133,6 @@
}
@Override
- public void reloadColors() {
- super.reloadColors();
- mView.reloadColors();
- }
-
- @Override
protected void verifyPasswordAndUnlock() {
String entry = mPasswordEntry.getText();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 5995e85..e9405eb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -121,12 +121,6 @@
}
@Override
- public void reloadColors() {
- super.reloadColors();
- mView.reloadColors();
- }
-
- @Override
protected void verifyPasswordAndUnlock() {
mStateMachine.next();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 331497e..c11a5c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -27,6 +27,8 @@
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
import static android.hardware.biometrics.BiometricConstants.LockoutMode;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
+import static android.hardware.biometrics.BiometricSourceType.FACE;
+import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
@@ -229,7 +231,15 @@
* Biometric authentication: Cancelling and waiting for the relevant biometric service to
* send us the confirmation that cancellation has happened.
*/
- private static final int BIOMETRIC_STATE_CANCELLING = 2;
+ @VisibleForTesting
+ protected static final int BIOMETRIC_STATE_CANCELLING = 2;
+
+ /**
+ * Biometric state: During cancelling we got another request to start listening, so when we
+ * receive the cancellation done signal, we should start listening again.
+ */
+ @VisibleForTesting
+ protected static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
/**
* Action indicating keyguard *can* start biometric authentiation.
@@ -244,12 +254,6 @@
*/
private static final int BIOMETRIC_ACTION_UPDATE = 2;
- /**
- * Biometric state: During cancelling we got another request to start listening, so when we
- * receive the cancellation done signal, we should start listening again.
- */
- private static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
-
@VisibleForTesting
public static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;
@@ -372,7 +376,8 @@
private KeyguardBypassController mKeyguardBypassController;
private List<SubscriptionInfo> mSubscriptionInfo;
- private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ @VisibleForTesting
+ protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
private boolean mIsDreaming;
private boolean mLogoutEnabled;
@@ -746,8 +751,8 @@
*/
public void requestFaceAuthOnOccludingApp(boolean request) {
mOccludingAppRequestingFace = request;
- updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED);
+ int action = mOccludingAppRequestingFace ? BIOMETRIC_ACTION_UPDATE : BIOMETRIC_ACTION_STOP;
+ updateFaceListeningState(action, FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED);
}
/**
@@ -806,7 +811,7 @@
new BiometricAuthenticated(true, isStrongBiometric));
// Update/refresh trust state only if user can skip bouncer
if (getUserCanSkipBouncer(userId)) {
- mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FINGERPRINT);
+ mTrustManager.unlockedByBiometricForUser(userId, FINGERPRINT);
}
// Don't send cancel if authentication succeeds
mFingerprintCancelSignal = null;
@@ -816,7 +821,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT,
+ cb.onBiometricAuthenticated(userId, FINGERPRINT,
isStrongBiometric);
}
}
@@ -849,7 +854,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+ cb.onBiometricAuthFailed(FINGERPRINT);
}
}
if (isUdfpsSupported()) {
@@ -874,7 +879,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAcquired(BiometricSourceType.FINGERPRINT, acquireInfo);
+ cb.onBiometricAcquired(FINGERPRINT, acquireInfo);
}
}
}
@@ -908,7 +913,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FINGERPRINT);
+ cb.onBiometricHelp(msgId, helpString, FINGERPRINT);
}
}
}
@@ -960,7 +965,7 @@
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged = !mFingerprintLockedOutPermanent;
mFingerprintLockedOutPermanent = true;
- mLogger.d("Fingerprint locked out - requiring strong auth");
+ mLogger.d("Fingerprint permanently locked out - requiring stronger auth");
mLockPatternUtils.requireStrongAuth(
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
}
@@ -969,6 +974,7 @@
|| msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged |= !mFingerprintLockedOut;
mFingerprintLockedOut = true;
+ mLogger.d("Fingerprint temporarily locked out - requiring stronger auth");
if (isUdfpsEnrolled()) {
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
@@ -979,12 +985,12 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT);
+ cb.onBiometricError(msgId, errString, FINGERPRINT);
}
}
if (lockedOutStateChanged) {
- notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+ notifyLockedOutStateChanged(FINGERPRINT);
}
}
@@ -1012,7 +1018,7 @@
}
if (changed) {
- notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+ notifyLockedOutStateChanged(FINGERPRINT);
}
}
@@ -1035,7 +1041,7 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricRunningStateChanged(isFingerprintDetectionRunning(),
- BiometricSourceType.FINGERPRINT);
+ FINGERPRINT);
}
}
}
@@ -1048,7 +1054,7 @@
new BiometricAuthenticated(true, isStrongBiometric));
// Update/refresh trust state only if user can skip bouncer
if (getUserCanSkipBouncer(userId)) {
- mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);
+ mTrustManager.unlockedByBiometricForUser(userId, FACE);
}
// Don't send cancel if authentication succeeds
mFaceCancelSignal = null;
@@ -1059,7 +1065,7 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricAuthenticated(userId,
- BiometricSourceType.FACE,
+ FACE,
isStrongBiometric);
}
}
@@ -1081,7 +1087,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAuthFailed(BiometricSourceType.FACE);
+ cb.onBiometricAuthFailed(FACE);
}
}
handleFaceHelp(BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
@@ -1094,7 +1100,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo);
+ cb.onBiometricAcquired(FACE, acquireInfo);
}
}
}
@@ -1129,7 +1135,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FACE);
+ cb.onBiometricHelp(msgId, helpString, FACE);
}
}
}
@@ -1197,12 +1203,12 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricError(msgId, errString,
- BiometricSourceType.FACE);
+ FACE);
}
}
if (lockedOutStateChanged) {
- notifyLockedOutStateChanged(BiometricSourceType.FACE);
+ notifyLockedOutStateChanged(FACE);
}
}
@@ -1216,7 +1222,7 @@
FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET), getBiometricLockoutDelay());
if (changed) {
- notifyLockedOutStateChanged(BiometricSourceType.FACE);
+ notifyLockedOutStateChanged(FACE);
}
}
@@ -1239,7 +1245,7 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricRunningStateChanged(isFaceDetectionRunning(),
- BiometricSourceType.FACE);
+ FACE);
}
}
}
@@ -1380,7 +1386,35 @@
}
public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
- return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric);
+ // StrongAuthTracker#isUnlockingWithBiometricAllowed includes
+ // STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
+ // however the strong auth tracker does not include the temporary lockout
+ // mFingerprintLockedOut.
+ return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric)
+ && !mFingerprintLockedOut;
+ }
+
+ /**
+ * Whether fingerprint is allowed ot be used for unlocking based on the strongAuthTracker
+ * and temporary lockout state (tracked by FingerprintManager via error codes).
+ */
+ public boolean isUnlockingWithFingerprintAllowed() {
+ return isUnlockingWithBiometricAllowed(FINGERPRINT);
+ }
+
+ /**
+ * Whether the given biometric is allowed based on strongAuth & lockout states.
+ */
+ public boolean isUnlockingWithBiometricAllowed(
+ @NonNull BiometricSourceType biometricSourceType) {
+ switch (biometricSourceType) {
+ case FINGERPRINT:
+ return isUnlockingWithBiometricAllowed(true);
+ case FACE:
+ return isUnlockingWithBiometricAllowed(false);
+ default:
+ return false;
+ }
}
public boolean isUserInLockdown(int userId) {
@@ -1402,11 +1436,6 @@
return isEncrypted || isLockDown;
}
- public boolean userNeedsStrongAuth() {
- return mStrongAuthTracker.getStrongAuthForUser(getCurrentUser())
- != LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
- }
-
private boolean containsFlag(int haystack, int needle) {
return (haystack & needle) != 0;
}
@@ -1576,12 +1605,6 @@
}
};
- private final FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback
- = (sensorId, userId, isStrongBiometric) -> {
- // Trigger the fingerprint success path so the bouncer can be shown
- handleFingerprintAuthenticated(userId, isStrongBiometric);
- };
-
/**
* Propagates a pointer down event to keyguard.
*/
@@ -2651,27 +2674,25 @@
&& (!mKeyguardGoingAway || !mDeviceInteractive)
&& mIsPrimaryUser
&& biometricEnabledForUser;
-
- final boolean shouldListenBouncerState = !(mFingerprintLockedOut
- && mPrimaryBouncerIsOrWillBeShowing && mCredentialAttempted);
-
- final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
+ final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
+ final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
+ final boolean shouldListenBouncerState =
+ !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
final boolean shouldListenUdfpsState = !isUdfps
|| (!userCanSkipBouncer
- && !isEncryptedOrLockdownForUser
+ && !strongerAuthRequired
&& userDoesNotHaveTrust);
boolean shouldListenSideFpsState = true;
- if (isSfpsSupported() && isSfpsEnrolled()) {
+ if (isSideFps) {
shouldListenSideFpsState =
mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
}
boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
- && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut()
+ && shouldListenBouncerState && shouldListenUdfpsState
&& shouldListenSideFpsState;
-
maybeLogListenerModelData(
new KeyguardFingerprintListenModel(
System.currentTimeMillis(),
@@ -2683,7 +2704,6 @@
mCredentialAttempted,
mDeviceInteractive,
mIsDreaming,
- isEncryptedOrLockdownForUser,
fingerprintDisabledForUser,
mFingerprintLockedOut,
mGoingToSleep,
@@ -2694,6 +2714,7 @@
mIsPrimaryUser,
shouldListenSideFpsState,
shouldListenForFingerprintAssistant,
+ strongerAuthRequired,
mSwitchingUser,
isUdfps,
userDoesNotHaveTrust));
@@ -2721,10 +2742,7 @@
final boolean isEncryptedOrTimedOut =
containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
|| containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
-
- // TODO: always disallow when fp is already locked out?
- final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
-
+ final boolean fpLockedOut = isFingerprintLockedOut();
final boolean canBypass = mKeyguardBypassController != null
&& mKeyguardBypassController.canBypass();
// There's no reason to ask the HAL for authentication when the user can dismiss the
@@ -2846,15 +2864,22 @@
// Waiting for restart via handleFingerprintError().
return;
}
- mLogger.v("startListeningForFingerprint()");
if (unlockPossible) {
mFingerprintCancelSignal = new CancellationSignal();
- if (isEncryptedOrLockdown(userId)) {
- mFpm.detectFingerprint(mFingerprintCancelSignal, mFingerprintDetectionCallback,
+ if (!isUnlockingWithFingerprintAllowed()) {
+ mLogger.v("startListeningForFingerprint - detect");
+ mFpm.detectFingerprint(
+ mFingerprintCancelSignal,
+ (sensorId, user, isStrongBiometric) -> {
+ mLogger.d("fingerprint detected");
+ // Trigger the fingerprint success path so the bouncer can be shown
+ handleFingerprintAuthenticated(user, isStrongBiometric);
+ },
userId);
} else {
+ mLogger.v("startListeningForFingerprint - authenticate");
mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
mFingerprintAuthenticationCallback, null /* handler */,
FingerprintManager.SENSOR_ID_ANY, userId, 0 /* flags */);
@@ -3071,11 +3096,15 @@
}
}
+ // Immediately stop previous biometric listening states.
+ // Resetting lockout states updates the biometric listening states.
if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
+ stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING);
handleFaceLockoutReset(mFaceManager.getLockoutModeForUser(
mFaceSensorProperties.get(0).sensorId, userId));
}
if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
+ stopListeningForFingerprint();
handleFingerprintLockoutReset(mFpm.getLockoutModeForUser(
mFingerprintSensorProperties.get(0).sensorId, userId));
}
@@ -3482,7 +3511,7 @@
@AnyThread
public void setSwitchingUser(boolean switching) {
mSwitchingUser = switching;
- // Since this comes in on a binder thread, we need to post if first
+ // Since this comes in on a binder thread, we need to post it first
mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_USER_SWITCHING));
}
@@ -3574,8 +3603,8 @@
Assert.isMainThread();
mUserFingerprintAuthenticated.clear();
mUserFaceAuthenticated.clear();
- mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT, unlockedUser);
- mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, unlockedUser);
+ mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser);
+ mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser);
mLogger.d("clearBiometricRecognized");
for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index 929ebea..becf5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -39,10 +39,13 @@
*/
public interface CoreStartable extends Dumpable {
- /** Main entry point for implementations. Called shortly after app startup. */
+ /** Main entry point for implementations. Called shortly after SysUI startup. */
void start();
- /** */
+ /** Called when the device configuration changes. This will not be called before
+ * {@link #start()}, but it could be called before {@link #onBootCompleted()}.
+ *
+ * @see android.app.Application#onConfigurationChanged(Configuration) */
default void onConfigurationChanged(Configuration newConfig) {
}
@@ -50,7 +53,11 @@
default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
}
- /** Called when the device reports BOOT_COMPLETED. */
+ /** Called immediately after the system broadcasts
+ * {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED} or during SysUI startup if the
+ * property {@code sys.boot_completed} is already set to 1. The latter typically occurs when
+ * starting a new SysUI instance, such as when starting SysUI for a secondary user.
+ * {@link #onBootCompleted()} will never be called before {@link #start()}. */
default void onBootCompleted() {
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index eb974dd..092339a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -598,6 +598,10 @@
getFingerprintSensorLocationInNaturalOrientation(),
mCachedDisplayInfo);
}
+
+ for (final Callback cb : mCallbacks) {
+ cb.onFingerprintLocationChanged();
+ }
}
/**
@@ -619,6 +623,10 @@
mCachedDisplayInfo
);
}
+
+ for (final Callback cb : mCallbacks) {
+ cb.onFaceSensorLocationChanged();
+ }
}
/**
@@ -1265,8 +1273,24 @@
default void onBiometricPromptDismissed() {}
/**
- * The location in pixels can change due to resolution changes.
+ * Called when the location of the fingerprint sensor changes. The location in pixels can
+ * change due to resolution changes.
+ */
+ default void onFingerprintLocationChanged() {}
+
+ /**
+ * Called when the location of the under display fingerprint sensor changes. The location in
+ * pixels can change due to resolution changes.
+ *
+ * On devices with UDFPS, this is always called alongside
+ * {@link #onFingerprintLocationChanged}.
*/
default void onUdfpsLocationChanged() {}
+
+ /**
+ * Called when the location of the face unlock sensor (typically the front facing camera)
+ * changes. The location in pixels can change due to resolution changes.
+ */
+ default void onFaceSensorLocationChanged() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 4363b88..d68fcd0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -29,6 +29,8 @@
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.CircleReveal
@@ -71,7 +73,8 @@
private val biometricUnlockController: BiometricUnlockController,
private val udfpsControllerProvider: Provider<UdfpsController>,
private val statusBarStateController: StatusBarStateController,
- rippleView: AuthRippleView?
+ private val featureFlags: FeatureFlags,
+ rippleView: AuthRippleView?
) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
WakefulnessLifecycle.Observer {
@@ -116,9 +119,9 @@
notificationShadeWindowController.setForcePluginOpen(false, this)
}
- fun showUnlockRipple(biometricSourceType: BiometricSourceType?) {
+ fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
if (!keyguardStateController.isShowing ||
- keyguardUpdateMonitor.userNeedsStrongAuth()) {
+ !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(biometricSourceType)) {
return
}
@@ -159,12 +162,17 @@
private fun showUnlockedRipple() {
notificationShadeWindowController.setForcePluginOpen(true, this)
- val lightRevealScrim = centralSurfaces.lightRevealScrim
- if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
- circleReveal?.let {
- lightRevealScrim?.revealAmount = 0f
- lightRevealScrim?.revealEffect = it
- startLightRevealScrimOnKeyguardFadingAway = true
+
+ // This code path is not used if the KeyguardTransitionRepository is managing the light
+ // reveal scrim.
+ if (!featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+ val lightRevealScrim = centralSurfaces.lightRevealScrim
+ if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
+ circleReveal?.let {
+ lightRevealScrim?.revealAmount = 0f
+ lightRevealScrim?.revealEffect = it
+ startLightRevealScrimOnKeyguardFadingAway = true
+ }
}
}
@@ -177,6 +185,10 @@
}
override fun onKeyguardFadingAwayChanged() {
+ if (featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+ return
+ }
+
if (keyguardStateController.isKeyguardFadingAway) {
val lightRevealScrim = centralSurfaces.lightRevealScrim
if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
@@ -246,7 +258,7 @@
object : KeyguardUpdateMonitorCallback() {
override fun onBiometricAuthenticated(
userId: Int,
- biometricSourceType: BiometricSourceType?,
+ biometricSourceType: BiometricSourceType,
isStrongBiometric: Boolean
) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
@@ -255,14 +267,14 @@
showUnlockRipple(biometricSourceType)
}
- override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
+ override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
mView.retractDwellRipple()
}
}
override fun onBiometricAcquired(
- biometricSourceType: BiometricSourceType?,
+ biometricSourceType: BiometricSourceType,
acquireInfo: Int
) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 1c3dd45..e09e65e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -370,11 +370,15 @@
private fun LottieAnimationView.addOverlayDynamicColor(context: Context) {
fun update() {
val c = context.getColor(R.color.biometric_dialog_accent)
+ val chevronFill = context.getColor(R.color.sfps_chevron_fill)
for (key in listOf(".blue600", ".blue400")) {
addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP)
}
}
+ addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
+ }
}
if (composition != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt
new file mode 100644
index 0000000..3d10ab9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls
+
+import kotlinx.coroutines.flow.StateFlow
+
+/** Repository for Device controls related settings. */
+interface ControlsSettingsRepository {
+ /** Whether device controls activity can be shown above lockscreen for this user. */
+ val canShowControlsInLockscreen: StateFlow<Boolean>
+
+ /** Whether trivial controls can be actioned from the lockscreen for this user. */
+ val allowActionOnTrivialControlsInLockscreen: StateFlow<Boolean>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt
new file mode 100644
index 0000000..9dc422a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls
+
+import android.provider.Settings
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.SettingObserver
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * This implementation uses an `@Application` [CoroutineScope] to provide hot flows for the values
+ * of the tracked settings.
+ */
+@SysUISingleton
+class ControlsSettingsRepositoryImpl
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val userRepository: UserRepository,
+ private val secureSettings: SecureSettings
+) : ControlsSettingsRepository {
+
+ override val canShowControlsInLockscreen =
+ makeFlowForSetting(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS)
+
+ override val allowActionOnTrivialControlsInLockscreen =
+ makeFlowForSetting(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun makeFlowForSetting(setting: String): StateFlow<Boolean> {
+ return userRepository.selectedUserInfo
+ .distinctUntilChanged()
+ .flatMapLatest { userInfo ->
+ conflatedCallbackFlow {
+ val observer =
+ object : SettingObserver(secureSettings, null, setting, userInfo.id) {
+ override fun handleValueChanged(
+ value: Int,
+ observedChange: Boolean
+ ) {
+ trySend(value == 1)
+ }
+ }
+ observer.isListening = true
+ trySend(observer.value == 1)
+ awaitClose { observer.isListening = false }
+ }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+ }
+ .stateIn(
+ scope,
+ started = SharingStarted.Eagerly,
+ // When the observer starts listening, the flow will emit the current value
+ // so the initialValue here is irrelevant.
+ initialValue = false,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 9e4a364..77d0496e4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -16,13 +16,10 @@
package com.android.systemui.controls.dagger
-import android.content.ContentResolver
import android.content.Context
-import android.database.ContentObserver
-import android.os.UserHandle
-import android.provider.Settings
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+import com.android.systemui.controls.ControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
@@ -31,12 +28,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.settings.SecureSettings
import dagger.Lazy
+import kotlinx.coroutines.flow.StateFlow
import java.util.Optional
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
/**
* Pseudo-component to inject into classes outside `com.android.systemui.controls`.
@@ -46,47 +41,26 @@
*/
@SysUISingleton
class ControlsComponent @Inject constructor(
- @ControlsFeatureEnabled private val featureEnabled: Boolean,
- private val context: Context,
- private val lazyControlsController: Lazy<ControlsController>,
- private val lazyControlsUiController: Lazy<ControlsUiController>,
- private val lazyControlsListingController: Lazy<ControlsListingController>,
- private val lockPatternUtils: LockPatternUtils,
- private val keyguardStateController: KeyguardStateController,
- private val userTracker: UserTracker,
- private val secureSettings: SecureSettings,
- private val optionalControlsTileResourceConfiguration:
- Optional<ControlsTileResourceConfiguration>
+ @ControlsFeatureEnabled private val featureEnabled: Boolean,
+ private val context: Context,
+ private val lazyControlsController: Lazy<ControlsController>,
+ private val lazyControlsUiController: Lazy<ControlsUiController>,
+ private val lazyControlsListingController: Lazy<ControlsListingController>,
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardStateController: KeyguardStateController,
+ private val userTracker: UserTracker,
+ controlsSettingsRepository: ControlsSettingsRepository,
+ optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration>
) {
- private val contentResolver: ContentResolver
- get() = context.contentResolver
- private val _canShowWhileLockedSetting = MutableStateFlow(false)
- val canShowWhileLockedSetting = _canShowWhileLockedSetting.asStateFlow()
+ val canShowWhileLockedSetting: StateFlow<Boolean> =
+ controlsSettingsRepository.canShowControlsInLockscreen
private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
optionalControlsTileResourceConfiguration.orElse(
ControlsTileResourceConfigurationImpl()
)
- val showWhileLockedObserver = object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- updateShowWhileLocked()
- }
- }
-
- init {
- if (featureEnabled) {
- secureSettings.registerContentObserverForUser(
- Settings.Secure.getUriFor(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS),
- false, /* notifyForDescendants */
- showWhileLockedObserver,
- UserHandle.USER_ALL
- )
- updateShowWhileLocked()
- }
- }
-
fun getControlsController(): Optional<ControlsController> {
return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
}
@@ -127,11 +101,6 @@
return Visibility.AVAILABLE
}
- private fun updateShowWhileLocked() {
- _canShowWhileLockedSetting.value = secureSettings.getIntForUser(
- Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
- }
-
enum class Visibility {
AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 6f58abd..9ae605e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -20,6 +20,8 @@
import android.content.pm.PackageManager
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsMetricsLoggerImpl
+import com.android.systemui.controls.ControlsSettingsRepository
+import com.android.systemui.controls.ControlsSettingsRepositoryImpl
import com.android.systemui.controls.controller.ControlsBindingController
import com.android.systemui.controls.controller.ControlsBindingControllerImpl
import com.android.systemui.controls.controller.ControlsController
@@ -83,6 +85,11 @@
abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
@Binds
+ abstract fun provideSettingsManager(
+ manager: ControlsSettingsRepositoryImpl
+ ): ControlsSettingsRepository
+
+ @Binds
abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index b8a0013..8472ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -24,9 +24,6 @@
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
-import android.database.ContentObserver
-import android.net.Uri
-import android.os.Handler
import android.os.UserHandle
import android.os.VibrationEffect
import android.provider.Settings.Secure
@@ -40,6 +37,7 @@
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -68,17 +66,17 @@
private val vibrator: VibratorHelper,
private val secureSettings: SecureSettings,
private val userContextProvider: UserContextProvider,
- @Main mainHandler: Handler
+ private val controlsSettingsRepository: ControlsSettingsRepository,
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private var pendingAction: Action? = null
private var actionsInProgress = mutableSetOf<String>()
private val isLocked: Boolean
get() = !keyguardStateController.isUnlocked()
- private var mAllowTrivialControls: Boolean = secureSettings.getIntForUser(
- Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
- private var mShowDeviceControlsInLockscreen: Boolean = secureSettings.getIntForUser(
- Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
+ private val allowTrivialControls: Boolean
+ get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
+ private val showDeviceControlsInLockscreen: Boolean
+ get() = controlsSettingsRepository.canShowControlsInLockscreen.value
override lateinit var activityContext: Context
companion object {
@@ -86,38 +84,6 @@
private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
}
- init {
- val lockScreenShowControlsUri =
- secureSettings.getUriFor(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)
- val showControlsUri =
- secureSettings.getUriFor(Secure.LOCKSCREEN_SHOW_CONTROLS)
- val controlsContentObserver = object : ContentObserver(mainHandler) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- super.onChange(selfChange, uri)
- when (uri) {
- lockScreenShowControlsUri -> {
- mAllowTrivialControls = secureSettings.getIntForUser(
- Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
- 0, UserHandle.USER_CURRENT) != 0
- }
- showControlsUri -> {
- mShowDeviceControlsInLockscreen = secureSettings
- .getIntForUser(Secure.LOCKSCREEN_SHOW_CONTROLS,
- 0, UserHandle.USER_CURRENT) != 0
- }
- }
- }
- }
- secureSettings.registerContentObserverForUser(
- lockScreenShowControlsUri,
- false /* notifyForDescendants */, controlsContentObserver, UserHandle.USER_ALL
- )
- secureSettings.registerContentObserverForUser(
- showControlsUri,
- false /* notifyForDescendants */, controlsContentObserver, UserHandle.USER_ALL
- )
- }
-
override fun closeDialogs() {
dialog?.dismiss()
dialog = null
@@ -224,7 +190,7 @@
@AnyThread
@VisibleForTesting
fun bouncerOrRun(action: Action) {
- val authRequired = action.authIsRequired || !mAllowTrivialControls
+ val authRequired = action.authIsRequired || !allowTrivialControls
if (keyguardStateController.isShowing() && authRequired) {
if (isLocked) {
@@ -282,7 +248,7 @@
PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)
if (attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG ||
- (mShowDeviceControlsInLockscreen && mAllowTrivialControls)) {
+ (showDeviceControlsInLockscreen && allowTrivialControls)) {
return
}
val builder = AlertDialog
@@ -304,7 +270,7 @@
true
}
- if (mShowDeviceControlsInLockscreen) {
+ if (showDeviceControlsInLockscreen) {
dialog = builder
.setTitle(R.string.controls_settings_trivial_controls_dialog_title)
.setMessage(R.string.controls_settings_trivial_controls_dialog_message)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index db19749..7c05356 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -164,6 +164,13 @@
// TODO(b/256513609): Tracking Bug
@JvmField val ACTIVE_UNLOCK_CHIPBAR = releasedFlag(217, "active_unlock_chipbar")
+ /**
+ * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the
+ * new KeyguardTransitionRepository.
+ */
+ @JvmField
+ val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = true)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -207,15 +214,6 @@
unreleasedFlag(508, "qs_secondary_data_sub_info", teamfood = true)
// 600- status bar
- // TODO(b/254512623): Tracking Bug
- @Deprecated("Replaced by mobile and wifi specific flags.")
- val NEW_STATUS_BAR_PIPELINE_BACKEND =
- unreleasedFlag(604, "new_status_bar_pipeline_backend", teamfood = false)
-
- // TODO(b/254512660): Tracking Bug
- @Deprecated("Replaced by mobile and wifi specific flags.")
- val NEW_STATUS_BAR_PIPELINE_FRONTEND =
- unreleasedFlag(605, "new_status_bar_pipeline_frontend", teamfood = false)
// TODO(b/256614753): Tracking Bug
val NEW_STATUS_BAR_MOBILE_ICONS = unreleasedFlag(606, "new_status_bar_mobile_icons")
@@ -233,6 +231,10 @@
// TODO(b/256623670): Tracking Bug
@JvmField val BATTERY_SHIELD_ICON = unreleasedFlag(610, "battery_shield_icon")
+ // TODO(b/260881289): Tracking Bug
+ val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
+ unreleasedFlag(611, "new_status_bar_icons_debug_coloring")
+
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
@@ -377,7 +379,7 @@
// TODO(b/255854141): Tracking Bug
@JvmField
val WM_ENABLE_PREDICTIVE_BACK_SYSUI =
- unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = false)
+ unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
// 1300 - screenshots
// TODO(b/254512719): Tracking Bug
@@ -403,17 +405,15 @@
// 1700 - clipboard
@JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
- @JvmField
- val CLIPBOARD_REMOTE_BEHAVIOR =
- unreleasedFlag(1701, "clipboard_remote_behavior", teamfood = true)
+ @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
// 1800 - shade container
@JvmField
val LEAVE_SHADE_OPEN_FOR_BUGREPORT =
unreleasedFlag(1800, "leave_shade_open_for_bugreport", teamfood = true)
- // 1900 - note task
- @JvmField val NOTE_TASKS = sysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
+ // 1900
+ @JvmField val NOTE_TASKS = unreleasedFlag(1900, "keycode_flag")
// 2000 - device controls
@Keep @JvmField val USE_APP_PANELS = unreleasedFlag(2000, "use_app_panels", teamfood = true)
@@ -430,6 +430,12 @@
// 2300 - stylus
@JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used")
+ // 2400 - performance tools and debugging info
+ // TODO(b/238923086): Tracking Bug
+ @JvmField
+ val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
+ unreleasedFlag(2400, "warn_on_blocking_binder_transactions")
+
// TODO(b259590361): Tracking bug
val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
index 49527d3..62fe80a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
@@ -21,50 +21,52 @@
import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.statusbar.policy.FlashlightController
+import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import javax.inject.Inject
@SysUISingleton
-class FlashlightQuickAffordanceConfig @Inject constructor(
- @Application private val context: Context,
- private val flashlightController: FlashlightController,
+class FlashlightQuickAffordanceConfig
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val flashlightController: FlashlightController,
) : KeyguardQuickAffordanceConfig {
private sealed class FlashlightState {
abstract fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState
- object On: FlashlightState() {
+ object On : FlashlightState() {
override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
- R.drawable.ic_flashlight_on,
+ R.drawable.qs_flashlight_icon_on,
ContentDescription.Resource(R.string.quick_settings_flashlight_label)
),
ActivationState.Active
)
}
- object OffAvailable: FlashlightState() {
+ object OffAvailable : FlashlightState() {
override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
- R.drawable.ic_flashlight_off,
+ R.drawable.qs_flashlight_icon_off,
ContentDescription.Resource(R.string.quick_settings_flashlight_label)
),
ActivationState.Inactive
)
}
- object Unavailable: FlashlightState() {
+ object Unavailable : FlashlightState() {
override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
}
@@ -77,57 +79,57 @@
get() = context.getString(R.string.quick_settings_flashlight_label)
override val pickerIconResourceId: Int
- get() = if (flashlightController.isEnabled) {
- R.drawable.ic_flashlight_on
- } else {
- R.drawable.ic_flashlight_off
- }
+ get() = R.drawable.ic_flashlight_off
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
- conflatedCallbackFlow {
- val flashlightCallback = object : FlashlightController.FlashlightListener {
- override fun onFlashlightChanged(enabled: Boolean) {
- trySendWithFailureLogging(
- if (enabled) {
- FlashlightState.On.toLockScreenState()
- } else {
- FlashlightState.OffAvailable.toLockScreenState()
- },
- TAG
- )
- }
+ conflatedCallbackFlow {
+ val flashlightCallback =
+ object : FlashlightController.FlashlightListener {
+ override fun onFlashlightChanged(enabled: Boolean) {
+ trySendWithFailureLogging(
+ if (enabled) {
+ FlashlightState.On.toLockScreenState()
+ } else {
+ FlashlightState.OffAvailable.toLockScreenState()
+ },
+ TAG
+ )
+ }
- override fun onFlashlightError() {
- trySendWithFailureLogging(FlashlightState.OffAvailable.toLockScreenState(), TAG)
- }
+ override fun onFlashlightError() {
+ trySendWithFailureLogging(
+ FlashlightState.OffAvailable.toLockScreenState(),
+ TAG
+ )
+ }
- override fun onFlashlightAvailabilityChanged(available: Boolean) {
- trySendWithFailureLogging(
- if (!available) {
- FlashlightState.Unavailable.toLockScreenState()
- } else {
- if (flashlightController.isEnabled) {
- FlashlightState.On.toLockScreenState()
- } else {
- FlashlightState.OffAvailable.toLockScreenState()
- }
- },
- TAG
- )
- }
+ override fun onFlashlightAvailabilityChanged(available: Boolean) {
+ trySendWithFailureLogging(
+ if (!available) {
+ FlashlightState.Unavailable.toLockScreenState()
+ } else {
+ if (flashlightController.isEnabled) {
+ FlashlightState.On.toLockScreenState()
+ } else {
+ FlashlightState.OffAvailable.toLockScreenState()
+ }
+ },
+ TAG
+ )
+ }
+ }
+
+ flashlightController.addCallback(flashlightCallback)
+
+ awaitClose { flashlightController.removeCallback(flashlightCallback) }
}
- flashlightController.addCallback(flashlightCallback)
-
- awaitClose {
- flashlightController.removeCallback(flashlightCallback)
- }
- }
-
- override fun onTriggered(expandable: Expandable?):
- KeyguardQuickAffordanceConfig.OnTriggeredResult {
- flashlightController
- .setFlashlight(flashlightController.isAvailable && !flashlightController.isEnabled)
+ override fun onTriggered(
+ expandable: Expandable?
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ flashlightController.setFlashlight(
+ flashlightController.isAvailable && !flashlightController.isEnabled
+ )
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
@@ -141,4 +143,4 @@
companion object {
private const val TAG = "FlashlightQuickAffordanceConfig"
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index 3013227c..072cfb1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -17,27 +17,35 @@
package com.android.systemui.keyguard.data.quickaffordance
+import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.ElementsIntoSet
@Module
-object KeyguardDataQuickAffordanceModule {
- @Provides
- @ElementsIntoSet
- fun quickAffordanceConfigs(
- flashlight: FlashlightQuickAffordanceConfig,
- home: HomeControlsKeyguardQuickAffordanceConfig,
- quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
- qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
- camera: CameraQuickAffordanceConfig,
- ): Set<KeyguardQuickAffordanceConfig> {
- return setOf(
- camera,
- flashlight,
- home,
- quickAccessWallet,
- qrCodeScanner,
- )
+interface KeyguardDataQuickAffordanceModule {
+ @Binds
+ fun providerClientFactory(
+ impl: KeyguardQuickAffordanceProviderClientFactoryImpl,
+ ): KeyguardQuickAffordanceProviderClientFactory
+
+ companion object {
+ @Provides
+ @ElementsIntoSet
+ fun quickAffordanceConfigs(
+ flashlight: FlashlightQuickAffordanceConfig,
+ home: HomeControlsKeyguardQuickAffordanceConfig,
+ quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+ qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+ camera: CameraQuickAffordanceConfig,
+ ): Set<KeyguardQuickAffordanceConfig> {
+ return setOf(
+ camera,
+ flashlight,
+ home,
+ quickAccessWallet,
+ qrCodeScanner,
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
index 766096f..72747f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
@@ -67,7 +67,7 @@
@Application private val scope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val secureSettings: SecureSettings,
- private val selectionsManager: KeyguardQuickAffordanceSelectionManager,
+ private val selectionsManager: KeyguardQuickAffordanceLocalUserSelectionManager,
) {
companion object {
private val BINDINGS =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
new file mode 100644
index 0000000..0066785
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.content.IntentFilter
+import android.content.SharedPreferences
+import com.android.systemui.R
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?" for the user associated with the
+ * System UI process.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardQuickAffordanceLocalUserSelectionManager
+@Inject
+constructor(
+ @Application context: Context,
+ private val userFileManager: UserFileManager,
+ private val userTracker: UserTracker,
+ broadcastDispatcher: BroadcastDispatcher,
+) : KeyguardQuickAffordanceSelectionManager {
+
+ private var sharedPrefs: SharedPreferences = instantiateSharedPrefs()
+
+ private val userId: Flow<Int> = conflatedCallbackFlow {
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ trySendWithFailureLogging(newUser, TAG)
+ }
+ }
+
+ userTracker.addCallback(callback) { it.run() }
+ trySendWithFailureLogging(userTracker.userId, TAG)
+
+ awaitClose { userTracker.removeCallback(callback) }
+ }
+
+ private val defaults: Map<String, List<String>> by lazy {
+ context.resources
+ .getStringArray(R.array.config_keyguardQuickAffordanceDefaults)
+ .associate { item ->
+ val splitUp = item.split(SLOT_AFFORDANCES_DELIMITER)
+ check(splitUp.size == 2)
+ val slotId = splitUp[0]
+ val affordanceIds = splitUp[1].split(AFFORDANCE_DELIMITER)
+ slotId to affordanceIds
+ }
+ }
+
+ /**
+ * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
+ * initial value.
+ */
+ private val backupRestorationEvents: Flow<Unit> =
+ broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
+ flags = Context.RECEIVER_NOT_EXPORTED,
+ permission = BackupHelper.PERMISSION_SELF,
+ )
+
+ override val selections: Flow<Map<String, List<String>>> =
+ combine(
+ userId,
+ backupRestorationEvents.onStart {
+ // We emit an initial event to make sure that the combine emits at least once,
+ // even if we never get a Backup & Restore restoration event (which is the most
+ // common case anyway as restoration really only happens on initial device
+ // setup).
+ emit(Unit)
+ }
+ ) { _, _ -> }
+ .flatMapLatest {
+ conflatedCallbackFlow {
+ // We want to instantiate a new SharedPreferences instance each time either the
+ // user ID changes or we have a backup & restore restoration event. The reason
+ // is that our sharedPrefs instance needs to be replaced with a new one as it
+ // depends on the user ID and when the B&R job completes, the backing file is
+ // replaced but the existing instance still has a stale in-memory cache.
+ sharedPrefs = instantiateSharedPrefs()
+
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
+ trySend(getSelections())
+ }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+ send(getSelections())
+
+ awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+ }
+
+ override fun getSelections(): Map<String, List<String>> {
+ val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) }
+ val result =
+ slotKeys
+ .associate { key ->
+ val slotId = key.substring(KEY_PREFIX_SLOT.length)
+ val value = sharedPrefs.getString(key, null)
+ val affordanceIds =
+ if (!value.isNullOrEmpty()) {
+ value.split(AFFORDANCE_DELIMITER)
+ } else {
+ emptyList()
+ }
+ slotId to affordanceIds
+ }
+ .toMutableMap()
+
+ // If the result map is missing keys, it means that the system has never set anything for
+ // those slots. This is where we need examine our defaults and see if there should be a
+ // default value for the affordances in the slot IDs that are missing from the result.
+ //
+ // Once the user makes any selection for a slot, even when they select "None", this class
+ // will persist a key for that slot ID. In the case of "None", it will have a value of the
+ // empty string. This is why this system works.
+ defaults.forEach { (slotId, affordanceIds) ->
+ if (!result.containsKey(slotId)) {
+ result[slotId] = affordanceIds
+ }
+ }
+
+ return result
+ }
+
+ override fun setSelections(
+ slotId: String,
+ affordanceIds: List<String>,
+ ) {
+ val key = "$KEY_PREFIX_SLOT$slotId"
+ val value = affordanceIds.joinToString(AFFORDANCE_DELIMITER)
+ sharedPrefs.edit().putString(key, value).apply()
+ }
+
+ private fun instantiateSharedPrefs(): SharedPreferences {
+ return userFileManager.getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId,
+ )
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordancePrimaryUserSelectionManager"
+ const val FILE_NAME = "quick_affordance_selections"
+ private const val KEY_PREFIX_SLOT = "slot_"
+ private const val SLOT_AFFORDANCES_DELIMITER = ":"
+ private const val AFFORDANCE_DELIMITER = ","
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt
new file mode 100644
index 0000000..727a813
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClientImpl
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+
+interface KeyguardQuickAffordanceProviderClientFactory {
+ fun create(): KeyguardQuickAffordanceProviderClient
+}
+
+class KeyguardQuickAffordanceProviderClientFactoryImpl
+@Inject
+constructor(
+ private val userTracker: UserTracker,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : KeyguardQuickAffordanceProviderClientFactory {
+ override fun create(): KeyguardQuickAffordanceProviderClient {
+ return KeyguardQuickAffordanceProviderClientImpl(
+ context = userTracker.userContext,
+ backgroundDispatcher = backgroundDispatcher,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt
new file mode 100644
index 0000000..8ffef25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?" for users associated with other System
+ * UI processes.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardQuickAffordanceRemoteUserSelectionManager
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val userTracker: UserTracker,
+ private val clientFactory: KeyguardQuickAffordanceProviderClientFactory,
+ private val userHandle: UserHandle,
+) : KeyguardQuickAffordanceSelectionManager {
+
+ private val userId: Flow<Int> = conflatedCallbackFlow {
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ trySendWithFailureLogging(newUser, TAG)
+ }
+ }
+
+ userTracker.addCallback(callback) { it.run() }
+ trySendWithFailureLogging(userTracker.userId, TAG)
+
+ awaitClose { userTracker.removeCallback(callback) }
+ }
+
+ private val clientOrNull: StateFlow<KeyguardQuickAffordanceProviderClient?> =
+ userId
+ .distinctUntilChanged()
+ .map { selectedUserId ->
+ if (userHandle.isSystem && userHandle.identifier != selectedUserId) {
+ clientFactory.create()
+ } else {
+ null
+ }
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+
+ private val _selections: StateFlow<Map<String, List<String>>> =
+ clientOrNull
+ .flatMapLatest { client ->
+ client?.observeSelections()?.map { selections ->
+ buildMap<String, List<String>> {
+ selections.forEach { selection ->
+ val slotId = selection.slotId
+ val affordanceIds = (get(slotId) ?: emptyList()).toMutableList()
+ affordanceIds.add(selection.affordanceId)
+ put(slotId, affordanceIds)
+ }
+ }
+ }
+ ?: emptyFlow()
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = emptyMap(),
+ )
+
+ override val selections: Flow<Map<String, List<String>>> = _selections
+
+ override fun getSelections(): Map<String, List<String>> {
+ return _selections.value
+ }
+
+ override fun setSelections(slotId: String, affordanceIds: List<String>) {
+ clientOrNull.value?.let { client ->
+ scope.launch {
+ client.deleteAllSelections(slotId = slotId)
+ affordanceIds.forEach { affordanceId ->
+ client.insertSelection(slotId = slotId, affordanceId = affordanceId)
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordanceMultiUserSelectionManager"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
index 4f37e5f..21fffed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -17,153 +17,22 @@
package com.android.systemui.keyguard.data.quickaffordance
-import android.content.Context
-import android.content.IntentFilter
-import android.content.SharedPreferences
-import com.android.systemui.R
-import com.android.systemui.backup.BackupHelper
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.settings.UserFileManager
-import com.android.systemui.settings.UserTracker
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.onStart
/**
- * Manages and provides access to the current "selections" of keyguard quick affordances, answering
- * the question "which affordances should the keyguard show?".
+ * Defines interface for classes that manage and provide access to the current "selections" of
+ * keyguard quick affordances, answering the question "which affordances should the keyguard show?".
*/
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class KeyguardQuickAffordanceSelectionManager
-@Inject
-constructor(
- @Application context: Context,
- private val userFileManager: UserFileManager,
- private val userTracker: UserTracker,
- broadcastDispatcher: BroadcastDispatcher,
-) {
-
- private var sharedPrefs: SharedPreferences = instantiateSharedPrefs()
-
- private val userId: Flow<Int> = conflatedCallbackFlow {
- val callback =
- object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- trySendWithFailureLogging(newUser, TAG)
- }
- }
-
- userTracker.addCallback(callback) { it.run() }
- trySendWithFailureLogging(userTracker.userId, TAG)
-
- awaitClose { userTracker.removeCallback(callback) }
- }
- private val defaults: Map<String, List<String>> by lazy {
- context.resources
- .getStringArray(R.array.config_keyguardQuickAffordanceDefaults)
- .associate { item ->
- val splitUp = item.split(SLOT_AFFORDANCES_DELIMITER)
- check(splitUp.size == 2)
- val slotId = splitUp[0]
- val affordanceIds = splitUp[1].split(AFFORDANCE_DELIMITER)
- slotId to affordanceIds
- }
- }
-
- /**
- * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
- * initial value.
- */
- private val backupRestorationEvents: Flow<Unit> =
- broadcastDispatcher.broadcastFlow(
- filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
- flags = Context.RECEIVER_NOT_EXPORTED,
- permission = BackupHelper.PERMISSION_SELF,
- )
+interface KeyguardQuickAffordanceSelectionManager {
/** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
- val selections: Flow<Map<String, List<String>>> =
- combine(
- userId,
- backupRestorationEvents.onStart {
- // We emit an initial event to make sure that the combine emits at least once,
- // even
- // if we never get a Backup & Restore restoration event (which is the most
- // common
- // case anyway as restoration really only happens on initial device setup).
- emit(Unit)
- }
- ) { _, _ ->
- }
- .flatMapLatest {
- conflatedCallbackFlow {
- // We want to instantiate a new SharedPreferences instance each time either the
- // user
- // ID changes or we have a backup & restore restoration event. The reason is
- // that
- // our sharedPrefs instance needs to be replaced with a new one as it depends on
- // the
- // user ID and when the B&R job completes, the backing file is replaced but the
- // existing instance still has a stale in-memory cache.
- sharedPrefs = instantiateSharedPrefs()
-
- val listener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
- trySend(getSelections())
- }
-
- sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
- send(getSelections())
-
- awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
- }
- }
+ val selections: Flow<Map<String, List<String>>>
/**
* Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
* descending priority order.
*/
- fun getSelections(): Map<String, List<String>> {
- val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) }
- val result =
- slotKeys
- .associate { key ->
- val slotId = key.substring(KEY_PREFIX_SLOT.length)
- val value = sharedPrefs.getString(key, null)
- val affordanceIds =
- if (!value.isNullOrEmpty()) {
- value.split(AFFORDANCE_DELIMITER)
- } else {
- emptyList()
- }
- slotId to affordanceIds
- }
- .toMutableMap()
-
- // If the result map is missing keys, it means that the system has never set anything for
- // those slots. This is where we need examine our defaults and see if there should be a
- // default value for the affordances in the slot IDs that are missing from the result.
- //
- // Once the user makes any selection for a slot, even when they select "None", this class
- // will persist a key for that slot ID. In the case of "None", it will have a value of the
- // empty string. This is why this system works.
- defaults.forEach { (slotId, affordanceIds) ->
- if (!result.containsKey(slotId)) {
- result[slotId] = affordanceIds
- }
- }
-
- return result
- }
+ fun getSelections(): Map<String, List<String>>
/**
* Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
@@ -172,25 +41,9 @@
fun setSelections(
slotId: String,
affordanceIds: List<String>,
- ) {
- val key = "$KEY_PREFIX_SLOT$slotId"
- val value = affordanceIds.joinToString(AFFORDANCE_DELIMITER)
- sharedPrefs.edit().putString(key, value).apply()
- }
-
- private fun instantiateSharedPrefs(): SharedPreferences {
- return userFileManager.getSharedPreferences(
- FILE_NAME,
- Context.MODE_PRIVATE,
- userTracker.userId,
- )
- }
+ )
companion object {
- private const val TAG = "KeyguardQuickAffordanceSelectionManager"
const val FILE_NAME = "quick_affordance_selections"
- private const val KEY_PREFIX_SLOT = "slot_"
- private const val SLOT_AFFORDANCES_DELIMITER = ":"
- private const val AFFORDANCE_DELIMITER = ","
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index d95a1a7..e3f5e90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -18,45 +18,93 @@
package com.android.systemui.keyguard.data.repository
import android.content.Context
+import android.os.UserHandle
import com.android.systemui.Dumpable
import com.android.systemui.R
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.settings.UserTracker
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Abstracts access to application state related to keyguard quick affordances. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardQuickAffordanceRepository
@Inject
constructor(
@Application private val appContext: Context,
@Application private val scope: CoroutineScope,
- private val selectionManager: KeyguardQuickAffordanceSelectionManager,
+ private val localUserSelectionManager: KeyguardQuickAffordanceLocalUserSelectionManager,
+ private val remoteUserSelectionManager: KeyguardQuickAffordanceRemoteUserSelectionManager,
+ private val userTracker: UserTracker,
legacySettingSyncer: KeyguardQuickAffordanceLegacySettingSyncer,
private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
dumpManager: DumpManager,
+ userHandle: UserHandle,
) {
+ private val userId: Flow<Int> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ trySendWithFailureLogging(newUser, TAG)
+ }
+ }
+
+ userTracker.addCallback(callback) { it.run() }
+ trySendWithFailureLogging(userTracker.userId, TAG)
+
+ awaitClose { userTracker.removeCallback(callback) }
+ }
+
+ private val selectionManager: StateFlow<KeyguardQuickAffordanceSelectionManager> =
+ userId
+ .distinctUntilChanged()
+ .map { selectedUserId ->
+ if (userHandle.identifier == selectedUserId) {
+ localUserSelectionManager
+ } else {
+ remoteUserSelectionManager
+ }
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = localUserSelectionManager,
+ )
+
/**
* List of [KeyguardQuickAffordanceConfig] instances of the affordances at the slot with the
* given ID. The configs are sorted in descending priority order.
*/
val selections: StateFlow<Map<String, List<KeyguardQuickAffordanceConfig>>> =
- selectionManager.selections
- .map { selectionsBySlotId ->
- selectionsBySlotId.mapValues { (_, selections) ->
- configs.filter { selections.contains(it.key) }
+ selectionManager
+ .flatMapLatest { selectionManager ->
+ selectionManager.selections.map { selectionsBySlotId ->
+ selectionsBySlotId.mapValues { (_, selections) ->
+ configs.filter { selections.contains(it.key) }
+ }
}
}
.stateIn(
@@ -99,7 +147,7 @@
* slot with the given ID. The configs are sorted in descending priority order.
*/
fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
- val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList())
+ val selections = selectionManager.value.getSelections().getOrDefault(slotId, emptyList())
return configs.filter { selections.contains(it.key) }
}
@@ -108,7 +156,7 @@
* are sorted in descending priority order.
*/
fun getSelections(): Map<String, List<String>> {
- return selectionManager.getSelections()
+ return selectionManager.value.getSelections()
}
/**
@@ -119,7 +167,7 @@
slotId: String,
affordanceIds: List<String>,
) {
- selectionManager.setSelections(
+ selectionManager.value.setSelections(
slotId = slotId,
affordanceIds = affordanceIds,
)
@@ -188,6 +236,7 @@
}
companion object {
+ private const val TAG = "KeyguardQuickAffordanceRepository"
private const val SLOT_CONFIG_DELIMITER = ":"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 796f2b4..148792b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -16,8 +16,11 @@
package com.android.systemui.keyguard.data.repository
+import android.graphics.Point
+import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.Position
@@ -27,8 +30,8 @@
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -88,8 +91,8 @@
* enter to conserve battery when the device is locked and inactive.
*
* Note that it is possible for the system to be transitioning into doze while this flow still
- * returns `false`. In order to account for that, observers should also use the [dozeAmount]
- * flow to check if it's greater than `0`
+ * returns `false`. In order to account for that, observers should also use the
+ * [linearDozeAmount] flow to check if it's greater than `0`
*/
val isDozing: Flow<Boolean>
@@ -111,7 +114,7 @@
* happens during an animation/transition into doze mode. An observer would be wise to account
* for both flows if needed.
*/
- val dozeAmount: Flow<Float>
+ val linearDozeAmount: Flow<Float>
/** Doze state information, as it transitions */
val dozeTransitionModel: Flow<DozeTransitionModel>
@@ -120,11 +123,20 @@
val statusBarState: Flow<StatusBarState>
/** Observable for device wake/sleep state */
- val wakefulnessState: Flow<WakefulnessModel>
+ val wakefulness: Flow<WakefulnessModel>
/** Observable for biometric unlock modes */
val biometricUnlockState: Flow<BiometricUnlockModel>
+ /** Approximate location on the screen of the fingerprint sensor. */
+ val fingerprintSensorLocation: Flow<Point?>
+
+ /** Approximate location on the screen of the face unlock sensor/front facing camera. */
+ val faceSensorLocation: Flow<Point?>
+
+ /** Source of the most recent biometric unlock, such as fingerprint or face. */
+ val biometricUnlockSource: Flow<BiometricUnlockSource?>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -163,6 +175,7 @@
private val keyguardStateController: KeyguardStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
+ private val authController: AuthController,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -281,11 +294,11 @@
}
.distinctUntilChanged()
- override val dozeAmount: Flow<Float> = conflatedCallbackFlow {
+ override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
val callback =
object : StatusBarStateController.StateListener {
override fun onDozeAmountChanged(linear: Float, eased: Float) {
- trySendWithFailureLogging(eased, TAG, "updated dozeAmount")
+ trySendWithFailureLogging(linear, TAG, "updated dozeAmount")
}
}
@@ -348,58 +361,139 @@
awaitClose { statusBarStateController.removeCallback(callback) }
}
- override val wakefulnessState: Flow<WakefulnessModel> = conflatedCallbackFlow {
- val callback =
- object : WakefulnessLifecycle.Observer {
- override fun onStartedWakingUp() {
- trySendWithFailureLogging(
- WakefulnessModel.STARTING_TO_WAKE,
- TAG,
- "Wakefulness: starting to wake"
- )
- }
- override fun onFinishedWakingUp() {
- trySendWithFailureLogging(WakefulnessModel.AWAKE, TAG, "Wakefulness: awake")
- }
- override fun onStartedGoingToSleep() {
- trySendWithFailureLogging(
- WakefulnessModel.STARTING_TO_SLEEP,
- TAG,
- "Wakefulness: starting to sleep"
- )
- }
- override fun onFinishedGoingToSleep() {
- trySendWithFailureLogging(WakefulnessModel.ASLEEP, TAG, "Wakefulness: asleep")
- }
- }
- wakefulnessLifecycle.addObserver(callback)
- trySendWithFailureLogging(
- wakefulnessIntToObject(wakefulnessLifecycle.getWakefulness()),
- TAG,
- "initial wakefulness state"
- )
-
- awaitClose { wakefulnessLifecycle.removeObserver(callback) }
- }
-
override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
+ fun dispatchUpdate() {
+ trySendWithFailureLogging(
+ biometricModeIntToObject(biometricUnlockController.mode),
+ TAG,
+ "biometric mode"
+ )
+ }
+
val callback =
object : BiometricUnlockController.BiometricModeListener {
override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
- trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode")
+ dispatchUpdate()
+ }
+
+ override fun onResetMode() {
+ dispatchUpdate()
}
}
biometricUnlockController.addBiometricModeListener(callback)
- trySendWithFailureLogging(
- biometricModeIntToObject(biometricUnlockController.getMode()),
- TAG,
- "initial biometric mode"
- )
+ dispatchUpdate()
awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
}
+ override val wakefulness: Flow<WakefulnessModel> = conflatedCallbackFlow {
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onStartedWakingUp() {
+ dispatchNewState()
+ }
+
+ override fun onFinishedWakingUp() {
+ dispatchNewState()
+ }
+
+ override fun onPostFinishedWakingUp() {
+ dispatchNewState()
+ }
+
+ override fun onStartedGoingToSleep() {
+ dispatchNewState()
+ }
+
+ override fun onFinishedGoingToSleep() {
+ dispatchNewState()
+ }
+
+ private fun dispatchNewState() {
+ trySendWithFailureLogging(
+ WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
+ TAG,
+ "updated wakefulness state"
+ )
+ }
+ }
+
+ wakefulnessLifecycle.addObserver(observer)
+ trySendWithFailureLogging(
+ WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
+ TAG,
+ "initial wakefulness state"
+ )
+
+ awaitClose { wakefulnessLifecycle.removeObserver(observer) }
+ }
+
+ override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
+ fun sendFpLocation() {
+ trySendWithFailureLogging(
+ authController.fingerprintSensorLocation,
+ TAG,
+ "AuthController.Callback#onFingerprintLocationChanged"
+ )
+ }
+
+ val callback =
+ object : AuthController.Callback {
+ override fun onFingerprintLocationChanged() {
+ sendFpLocation()
+ }
+ }
+
+ authController.addCallback(callback)
+ sendFpLocation()
+
+ awaitClose { authController.removeCallback(callback) }
+ }
+
+ override val faceSensorLocation: Flow<Point?> = conflatedCallbackFlow {
+ fun sendSensorLocation() {
+ trySendWithFailureLogging(
+ authController.faceSensorLocation,
+ TAG,
+ "AuthController.Callback#onFingerprintLocationChanged"
+ )
+ }
+
+ val callback =
+ object : AuthController.Callback {
+ override fun onFaceSensorLocationChanged() {
+ sendSensorLocation()
+ }
+ }
+
+ authController.addCallback(callback)
+ sendSensorLocation()
+
+ awaitClose { authController.removeCallback(callback) }
+ }
+
+ override val biometricUnlockSource: Flow<BiometricUnlockSource?> = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricAuthenticated(
+ userId: Int,
+ biometricSourceType: BiometricSourceType?,
+ isStrongBiometric: Boolean
+ ) {
+ trySendWithFailureLogging(
+ BiometricUnlockSource.fromBiometricSourceType(biometricSourceType),
+ TAG,
+ "onBiometricAuthenticated"
+ )
+ }
+ }
+
+ keyguardUpdateMonitor.registerCallback(callback)
+ trySendWithFailureLogging(null, TAG, "initial value")
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.value = animate
}
@@ -423,16 +517,6 @@
}
}
- private fun wakefulnessIntToObject(@Wakefulness value: Int): WakefulnessModel {
- return when (value) {
- 0 -> WakefulnessModel.ASLEEP
- 1 -> WakefulnessModel.STARTING_TO_WAKE
- 2 -> WakefulnessModel.AWAKE
- 3 -> WakefulnessModel.STARTING_TO_SLEEP
- else -> throw IllegalArgumentException("Invalid Wakefulness value: $value")
- }
- }
-
private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
return when (value) {
0 -> BiometricUnlockModel.NONE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 0c72520..26f853f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -27,4 +27,7 @@
fun keyguardTransitionRepository(
impl: KeyguardTransitionRepositoryImpl
): KeyguardTransitionRepository
+
+ @Binds
+ fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
new file mode 100644
index 0000000..a17481a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.data.repository
+
+import android.content.Context
+import android.graphics.Point
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.statusbar.CircleReveal
+import com.android.systemui.statusbar.LiftReveal
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.statusbar.PowerButtonReveal
+import javax.inject.Inject
+import kotlin.math.max
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+val DEFAULT_REVEAL_EFFECT = LiftReveal
+
+/**
+ * Encapsulates state relevant to the light reveal scrim, the view used to reveal/hide screen
+ * contents during transitions between AOD and lockscreen/unlocked.
+ */
+interface LightRevealScrimRepository {
+
+ /**
+ * The reveal effect that should be used for the next lock/unlock. We switch between either the
+ * biometric unlock effect (if wake and unlocking) or the non-biometric effect, and position it
+ * at the current screen position of the appropriate sensor.
+ */
+ val revealEffect: Flow<LightRevealEffect>
+}
+
+@SysUISingleton
+class LightRevealScrimRepositoryImpl
+@Inject
+constructor(
+ keyguardRepository: KeyguardRepository,
+ val context: Context,
+) : LightRevealScrimRepository {
+
+ /** The reveal effect used if the device was locked/unlocked via the power button. */
+ private val powerButtonReveal =
+ PowerButtonReveal(
+ context.resources
+ .getDimensionPixelSize(R.dimen.physical_power_button_center_screen_location_y)
+ .toFloat()
+ )
+
+ /**
+ * Reveal effect to use for a fingerprint unlock. This is reconstructed if the fingerprint
+ * sensor location on the screen (in pixels) changes due to configuration changes.
+ */
+ private val fingerprintRevealEffect: Flow<LightRevealEffect?> =
+ keyguardRepository.fingerprintSensorLocation.map {
+ it?.let { constructCircleRevealFromPoint(it) }
+ }
+
+ /**
+ * Reveal effect to use for a face unlock. This is reconstructed if the face sensor/front camera
+ * location on the screen (in pixels) changes due to configuration changes.
+ */
+ private val faceRevealEffect: Flow<LightRevealEffect?> =
+ keyguardRepository.faceSensorLocation.map { it?.let { constructCircleRevealFromPoint(it) } }
+
+ /**
+ * The reveal effect we'll use for the next biometric unlock animation. We switch between the
+ * fingerprint/face unlock effect flows depending on the biometric unlock source.
+ */
+ private val biometricRevealEffect: Flow<LightRevealEffect?> =
+ keyguardRepository.biometricUnlockSource.flatMapLatest { source ->
+ when (source) {
+ BiometricUnlockSource.FINGERPRINT_SENSOR -> fingerprintRevealEffect
+ BiometricUnlockSource.FACE_SENSOR -> faceRevealEffect
+ else -> flowOf(null)
+ }
+ }
+
+ /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
+ private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
+ keyguardRepository.wakefulness.map { wakefulnessModel ->
+ val wakingUpFromPowerButton =
+ wakefulnessModel.isWakingUpOrAwake &&
+ wakefulnessModel.lastWakeReason == WakeSleepReason.POWER_BUTTON
+ val sleepingFromPowerButton =
+ !wakefulnessModel.isWakingUpOrAwake &&
+ wakefulnessModel.lastSleepReason == WakeSleepReason.POWER_BUTTON
+
+ if (wakingUpFromPowerButton || sleepingFromPowerButton) {
+ powerButtonReveal
+ } else {
+ LiftReveal
+ }
+ }
+
+ override val revealEffect =
+ combine(
+ keyguardRepository.biometricUnlockState,
+ biometricRevealEffect,
+ nonBiometricRevealEffect
+ ) { biometricUnlockState, biometricReveal, nonBiometricReveal ->
+
+ // Use the biometric reveal for any flavor of wake and unlocking.
+ when (biometricUnlockState) {
+ BiometricUnlockModel.WAKE_AND_UNLOCK,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM -> biometricReveal
+ else -> nonBiometricReveal
+ }
+ ?: DEFAULT_REVEAL_EFFECT
+ }
+ .distinctUntilChanged()
+
+ private fun constructCircleRevealFromPoint(point: Point): LightRevealEffect {
+ return with(point) {
+ CircleReveal(
+ x,
+ y,
+ startRadius = 0,
+ endRadius =
+ max(max(x, context.display.width - x), max(y, context.display.height - y)),
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
index 2dbacd5..9b19353 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -27,7 +27,6 @@
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
index 9e2b724..e34981e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
@@ -42,7 +42,7 @@
override fun start() {
scope.launch {
- keyguardInteractor.wakefulnessState
+ keyguardInteractor.wakefulnessModel
.sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
.collect { pair ->
val (wakefulnessState, keyguardState) = pair
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
index 0e2a54c..483041a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
@@ -23,11 +23,10 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@SysUISingleton
@@ -42,13 +41,13 @@
override fun start() {
scope.launch {
- keyguardInteractor.wakefulnessState
+ keyguardInteractor.wakefulnessModel
.sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
.collect { pair ->
val (wakefulnessState, keyguardState) = pair
if (
keyguardState == KeyguardState.GONE &&
- wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP
+ wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 7cfd117..6912e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
+import android.graphics.Point
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
@@ -41,7 +42,7 @@
* The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
* all.
*/
- val dozeAmount: Flow<Float> = repository.dozeAmount
+ val dozeAmount: Flow<Float> = repository.linearDozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
/** Doze transition information. */
@@ -58,7 +59,7 @@
/** Whether the bouncer is showing or not. */
val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
/** The device wake/sleep state */
- val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState
+ val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState
/**
@@ -67,10 +68,15 @@
*/
val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
+ /** The approximate location on the screen of the fingerprint sensor, if one is available. */
+ val fingerprintSensorLocation: Flow<Point?> = repository.fingerprintSensorLocation
+
+ /** The approximate location on the screen of the face unlock sensor, if one is available. */
+ val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
+
fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> {
return dozeTransitionModel.filter { it.to == state }
}
-
fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index ee7154f..748c6e8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -190,8 +190,6 @@
/** Returns affordance IDs indexed by slot ID, for all known slots. */
suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
- check(isUsingRepository)
-
val slots = repository.get().getSlotPickerRepresentations()
val selections = repository.get().getSelections()
val affordanceById =
@@ -312,8 +310,6 @@
suspend fun getAffordancePickerRepresentations():
List<KeyguardQuickAffordancePickerRepresentation> {
- check(isUsingRepository)
-
return repository.get().getAffordancePickerRepresentations()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 58a8093..e30e7f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -37,7 +37,7 @@
fun start() {
scope.launch {
- keyguardInteractor.wakefulnessState.collect { logger.v("WakefulnessState", it) }
+ keyguardInteractor.wakefulnessModel.collect { logger.v("WakefulnessModel", it) }
}
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
new file mode 100644
index 0000000..6e25200
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class LightRevealScrimInteractor
+@Inject
+constructor(
+ transitionRepository: KeyguardTransitionRepository,
+ transitionInteractor: KeyguardTransitionInteractor,
+ lightRevealScrimRepository: LightRevealScrimRepository,
+) {
+
+ /**
+ * Whenever a keyguard transition starts, sample the latest reveal effect from the repository
+ * and use that for the starting transition.
+ *
+ * We can't simply use the nextRevealEffect since the effect may change midway through a
+ * transition, but we don't want to change effects part way through. For example, if we're using
+ * a CircleReveal to animate a biometric unlock, but the biometric unlock mode changes to NONE
+ * from WAKE_AND_UNLOCK before the unlock animation ends, we don't want to end up switching to a
+ * LiftReveal.
+ */
+ val lightRevealEffect: Flow<LightRevealEffect> =
+ transitionInteractor.startedKeyguardTransitionStep.sample(
+ lightRevealScrimRepository.revealEffect
+ )
+
+ /**
+ * The reveal amount to use for the light reveal scrim, which is derived from the keyguard
+ * transition steps.
+ */
+ val revealAmount: Flow<Float> =
+ transitionRepository.transitions
+ // Only listen to transitions that change the reveal amount.
+ .filter { willTransitionAffectRevealAmount(it) }
+ // Use the transition amount as the reveal amount, inverting it if we're transitioning
+ // to a non-revealed (hidden) state.
+ .map { step -> if (willBeRevealedInState(step.to)) step.value else 1f - step.value }
+
+ companion object {
+
+ /**
+ * Whether the transition requires a change in the reveal amount of the light reveal scrim.
+ * If not, we don't care about the transition and don't need to listen to it.
+ */
+ fun willTransitionAffectRevealAmount(transition: TransitionStep): Boolean {
+ return willBeRevealedInState(transition.from) != willBeRevealedInState(transition.to)
+ }
+
+ /**
+ * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
+ * state after the transition is complete. If false, scrim will be fully hidden.
+ */
+ fun willBeRevealedInState(state: KeyguardState): Boolean {
+ return when (state) {
+ KeyguardState.OFF -> false
+ KeyguardState.DOZING -> false
+ KeyguardState.AOD -> false
+ KeyguardState.DREAMING -> true
+ KeyguardState.BOUNCER -> true
+ KeyguardState.LOCKSCREEN -> true
+ KeyguardState.GONE -> true
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index 3bb8241..3218f96 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -25,7 +25,7 @@
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
import java.util.UUID
@@ -58,22 +58,26 @@
keyguardInteractor.isBouncerShowing
.sample(
combine(
- keyguardInteractor.wakefulnessState,
+ keyguardInteractor.wakefulnessModel,
keyguardTransitionInteractor.startedKeyguardTransitionStep,
- ) { a, b ->
- Pair(a, b)
- },
- { a, bc -> Triple(a, bc.first, bc.second) }
- )
- .collect { triple ->
- val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
+ ) { wakefulnessModel, transitionStep ->
+ Pair(wakefulnessModel, transitionStep)
+ }
+ ) { bouncerShowing, wakefulnessAndTransition ->
+ Triple(
+ bouncerShowing,
+ wakefulnessAndTransition.first,
+ wakefulnessAndTransition.second
+ )
+ }
+ .collect { (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) ->
if (
!isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
) {
val to =
if (
- wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
- wakefulnessState == WakefulnessModel.ASLEEP
+ wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
+ wakefulnessState.state == WakefulnessState.ASLEEP
) {
KeyguardState.AOD
} else {
@@ -100,14 +104,17 @@
combine(
keyguardTransitionInteractor.finishedKeyguardState,
keyguardInteractor.statusBarState,
- ) { a, b ->
- Pair(a, b)
- },
- { a, bc -> Triple(a, bc.first, bc.second) }
- )
- .collect { triple ->
- val (shadeModel, keyguardState, statusBarState) = triple
-
+ ) { finishedKeyguardState, statusBarState ->
+ Pair(finishedKeyguardState, statusBarState)
+ }
+ ) { shadeModel, keyguardStateAndStatusBarState ->
+ Triple(
+ shadeModel,
+ keyguardStateAndStatusBarState.first,
+ keyguardStateAndStatusBarState.second
+ )
+ }
+ .collect { (shadeModel, keyguardState, statusBarState) ->
val id = transitionId
if (id != null) {
// An existing `id` means a transition is started, and calls to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 84a8074..2cf5fb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.res.ColorStateList
+import android.hardware.biometrics.BiometricSourceType
import android.os.Handler
import android.os.Trace
import android.os.UserHandle
@@ -71,7 +72,7 @@
KeyguardUpdateMonitor.getCurrentUser()
) &&
!needsFullscreenBouncer() &&
- !keyguardUpdateMonitor.userNeedsStrongAuth() &&
+ keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
!keyguardBypassController.bypassEnabled
/** Runnable to show the primary bouncer. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.kt
new file mode 100644
index 0000000..b403416
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import android.hardware.biometrics.BiometricSourceType
+
+/** Biometric unlock sensor sources, which we use to play sensor-specific animations. */
+enum class BiometricUnlockSource {
+ /** The unlock was initiated by a fingerprint sensor authentication. */
+ FINGERPRINT_SENSOR,
+
+ /** The unlock was initiated by the front-facing camera or a nearby sensor. */
+ FACE_SENSOR;
+
+ companion object {
+ fun fromBiometricSourceType(type: BiometricSourceType?): BiometricUnlockSource? {
+ return when (type) {
+ BiometricSourceType.FINGERPRINT -> FINGERPRINT_SENSOR
+ BiometricSourceType.FACE -> FACE_SENSOR
+ BiometricSourceType.IRIS -> FACE_SENSOR
+ else -> null
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
new file mode 100644
index 0000000..b32597d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import android.os.PowerManager
+
+/** The reason we're waking up or going to sleep, such as pressing the power button. */
+enum class WakeSleepReason {
+ /** The physical power button was pressed to wake up or sleep the device. */
+ POWER_BUTTON,
+
+ /** Something else happened to wake up or sleep the device. */
+ OTHER;
+
+ companion object {
+ fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason {
+ return when (reason) {
+ PowerManager.WAKE_REASON_POWER_BUTTON -> POWER_BUTTON
+ else -> OTHER
+ }
+ }
+
+ fun fromPowerManagerSleepReason(reason: Int): WakeSleepReason {
+ return when (reason) {
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON -> POWER_BUTTON
+ else -> OTHER
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index 92040f4..03dee00 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -15,24 +15,34 @@
*/
package com.android.systemui.keyguard.shared.model
-/** Model device wakefulness states. */
-enum class WakefulnessModel {
- /** The device is asleep and not interactive. */
- ASLEEP,
- /** Received a signal that the device is beginning to wake up. */
- STARTING_TO_WAKE,
- /** Device is now fully awake and interactive. */
- AWAKE,
- /** Signal that the device is now going to sleep. */
- STARTING_TO_SLEEP;
+import com.android.systemui.keyguard.WakefulnessLifecycle
+/** Model device wakefulness states. */
+data class WakefulnessModel(
+ val state: WakefulnessState,
+ val isWakingUpOrAwake: Boolean,
+ val lastWakeReason: WakeSleepReason,
+ val lastSleepReason: WakeSleepReason,
+) {
companion object {
fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean {
- return model == ASLEEP || model == STARTING_TO_SLEEP
+ return model.state == WakefulnessState.ASLEEP ||
+ model.state == WakefulnessState.STARTING_TO_SLEEP
}
fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean {
- return model == AWAKE || model == STARTING_TO_WAKE
+ return model.state == WakefulnessState.AWAKE ||
+ model.state == WakefulnessState.STARTING_TO_WAKE
+ }
+
+ fun fromWakefulnessLifecycle(wakefulnessLifecycle: WakefulnessLifecycle): WakefulnessModel {
+ return WakefulnessModel(
+ WakefulnessState.fromWakefulnessLifecycleInt(wakefulnessLifecycle.wakefulness),
+ wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_WAKING ||
+ wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE,
+ WakeSleepReason.fromPowerManagerWakeReason(wakefulnessLifecycle.lastWakeReason),
+ WakeSleepReason.fromPowerManagerSleepReason(wakefulnessLifecycle.lastSleepReason),
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt
new file mode 100644
index 0000000..6791d88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import com.android.systemui.keyguard.WakefulnessLifecycle
+
+enum class WakefulnessState {
+ /** The device is asleep and not interactive. */
+ ASLEEP,
+ /** Received a signal that the device is beginning to wake up. */
+ STARTING_TO_WAKE,
+ /** Device is now fully awake and interactive. */
+ AWAKE,
+ /** Signal that the device is now going to sleep. */
+ STARTING_TO_SLEEP;
+
+ companion object {
+ fun fromWakefulnessLifecycleInt(
+ @WakefulnessLifecycle.Wakefulness value: Int
+ ): WakefulnessState {
+ return when (value) {
+ WakefulnessLifecycle.WAKEFULNESS_ASLEEP -> ASLEEP
+ WakefulnessLifecycle.WAKEFULNESS_WAKING -> STARTING_TO_WAKE
+ WakefulnessLifecycle.WAKEFULNESS_AWAKE -> AWAKE
+ WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP -> STARTING_TO_SLEEP
+ else -> throw IllegalArgumentException("Invalid Wakefulness value: $value")
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 3276b6d..cbe512f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.binder
+import android.graphics.drawable.Animatable2
import android.util.Size
import android.util.TypedValue
import android.view.View
@@ -27,12 +28,11 @@
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.LockIconViewController
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.Interpolators
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
@@ -73,7 +73,8 @@
fun onConfigurationChanged()
/**
- * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+ * Returns whether the keyguard bottom area should be constrained to the top of the lock
+ * icon
*/
fun shouldConstrainToTopOfLockIcon(): Boolean
}
@@ -217,7 +218,7 @@
}
override fun shouldConstrainToTopOfLockIcon(): Boolean =
- viewModel.shouldConstrainToTopOfLockIcon()
+ viewModel.shouldConstrainToTopOfLockIcon()
}
}
@@ -248,6 +249,27 @@
IconViewBinder.bind(viewModel.icon, view)
+ (view.drawable as? Animatable2)?.let { animatable ->
+ (viewModel.icon as? Icon.Resource)?.res?.let { iconResourceId ->
+ // Always start the animation (we do call stop() below, if we need to skip it).
+ animatable.start()
+
+ if (view.tag != iconResourceId) {
+ // Here when we haven't run the animation on a previous update.
+ //
+ // Save the resource ID for next time, so we know not to re-animate the same
+ // animation again.
+ view.tag = iconResourceId
+ } else {
+ // Here when we've already done this animation on a previous update and want to
+ // skip directly to the final frame of the animation to avoid running it.
+ //
+ // By calling stop after start, we go to the final frame of the animation.
+ animatable.stop()
+ }
+ }
+ }
+
view.isActivated = viewModel.isActivated
view.drawable.setTint(
Utils.getColorAttrDefaultColor(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
new file mode 100644
index 0000000..f1da882
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.LightRevealScrim
+import kotlinx.coroutines.launch
+
+object LightRevealScrimViewBinder {
+ @JvmStatic
+ fun bind(revealScrim: LightRevealScrim, viewModel: LightRevealScrimViewModel) {
+ revealScrim.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ viewModel.revealAmount.collect { amount -> revealScrim.revealAmount = amount }
+ }
+
+ launch {
+ viewModel.lightRevealEffect.collect { effect ->
+ revealScrim.revealEffect = effect
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
new file mode 100644
index 0000000..a46d441
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.LightRevealScrimInteractor
+import com.android.systemui.statusbar.LightRevealEffect
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Models UI state for the light reveal scrim, which is used during screen on and off animations to
+ * draw a gradient that reveals/hides the contents of the screen.
+ */
+class LightRevealScrimViewModel @Inject constructor(interactor: LightRevealScrimInteractor) {
+ val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect
+ val revealAmount: Flow<Float> = interactor.revealAmount
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 8aaee81..1fdbc99 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -184,6 +184,7 @@
private val configListener =
object : ConfigurationController.ConfigurationListener {
+
override fun onDensityOrFontScaleChanged() {
// System font changes should only happen when UMO is offscreen or a flicker may
// occur
@@ -199,6 +200,7 @@
override fun onConfigChanged(newConfig: Configuration?) {
if (newConfig == null) return
isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
+ updatePlayers(recreateMedia = true)
}
override fun onUiModeChanged() {
@@ -635,7 +637,7 @@
val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
existingSmartspaceMediaKey?.let {
val removedPlayer =
- MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey, true)
+ removePlayer(existingSmartspaceMediaKey, dismissMediaData = false)
removedPlayer?.run {
debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
}
@@ -685,7 +687,7 @@
key: String,
dismissMediaData: Boolean = true,
dismissRecommendation: Boolean = true
- ) {
+ ): MediaControlPanel? {
if (key == MediaPlayerData.smartspaceMediaKey()) {
MediaPlayerData.smartspaceMediaData?.let {
logger.logRecommendationRemoved(it.packageName, it.instanceId)
@@ -693,7 +695,7 @@
}
val removed =
MediaPlayerData.removeMediaPlayer(key, dismissMediaData || dismissRecommendation)
- removed?.apply {
+ return removed?.apply {
mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
mediaContent.removeView(removed.mediaViewHolder?.player)
mediaContent.removeView(removed.recommendationViewHolder?.recommendations)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 21e64e2..827ac78 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -458,7 +458,9 @@
if (mMediaViewHolder == null) {
return;
}
- Trace.beginSection("MediaControlPanel#bindPlayer<" + key + ">");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP, "MediaControlPanel#bindPlayer<" + key + ">");
+ }
mKey = key;
mMediaData = data;
MediaSession.Token token = data.getToken();
@@ -1179,8 +1181,10 @@
return;
}
- Trace.beginSection(
- "MediaControlPanel#bindRecommendation<" + data.getPackageName() + ">");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP,
+ "MediaControlPanel#bindRecommendation<" + data.getPackageName() + ">");
+ }
mRecommendationData = data;
mSmartspaceId = SmallHash.hash(data.getTargetId());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 57b256e..a6447a5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -579,9 +579,16 @@
private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner) {
- withWindowAttached(() ->
+ withWindowAttached(() -> {
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+ && mUserManager.isManagedProfile(owner.getIdentifier())) {
+ mScreenshotView.announceForAccessibility(mContext.getResources().getString(
+ R.string.screenshot_saving_work_profile_title));
+ } else {
mScreenshotView.announceForAccessibility(
- mContext.getResources().getString(R.string.screenshot_saving_title)));
+ mContext.getResources().getString(R.string.screenshot_saving_title));
+ }
+ });
mScreenshotView.reset();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 7641554..fae938d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -825,12 +825,23 @@
}
});
if (mQuickShareChip != null) {
- mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
- () -> {
- mUiEventLogger.log(
- ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0, mPackageName);
- animateDismissal();
- });
+ if (imageData.quickShareAction != null) {
+ mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
+ () -> {
+ mUiEventLogger.log(
+ ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0,
+ mPackageName);
+ animateDismissal();
+ });
+ } else {
+ // hide chip and unset pending interaction if necessary, since we don't actually
+ // have a useable quick share intent
+ Log.wtf(TAG, "Showed quick share chip, but quick share intent was null");
+ if (mPendingInteraction == PendingInteraction.QUICK_SHARE) {
+ mPendingInteraction = null;
+ }
+ mQuickShareChip.setVisibility(GONE);
+ }
}
if (mPendingInteraction != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index dcf264e..f52ace7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -393,6 +393,9 @@
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private int mQsTrackingPointer;
private VelocityTracker mQsVelocityTracker;
+ private TrackingStartedListener mTrackingStartedListener;
+ private OpenCloseListener mOpenCloseListener;
+ private GestureRecorder mGestureRecorder;
private boolean mQsTracking;
/** Whether the ongoing gesture might both trigger the expansion in both the view and QS. */
private boolean mConflictingQsExpansionGesture;
@@ -1362,6 +1365,14 @@
mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
}
+ void setOpenCloseListener(OpenCloseListener openCloseListener) {
+ mOpenCloseListener = openCloseListener;
+ }
+
+ void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
+ mTrackingStartedListener = trackingStartedListener;
+ }
+
private void updateGestureExclusionRect() {
Rect exclusionRect = calculateGestureExclusionRect();
mView.setSystemGestureExclusionRects(exclusionRect.isEmpty() ? Collections.emptyList()
@@ -1936,9 +1947,9 @@
}
private void fling(float vel) {
- GestureRecorder gr = mCentralSurfaces.getGestureRecorder();
- if (gr != null) {
- gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
+ if (mGestureRecorder != null) {
+ mGestureRecorder.tag("fling " + ((vel > 0) ? "open" : "closed"),
+ "notifications,v=" + vel);
}
fling(vel, true, 1.0f /* collapseSpeedUpFactor */, false);
}
@@ -3726,7 +3737,7 @@
mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
endClosing();
mTracking = true;
- mCentralSurfaces.onTrackingStarted();
+ mTrackingStartedListener.onTrackingStarted();
notifyExpandingStarted();
updatePanelExpansionAndVisibility();
mScrimController.onTrackingStarted();
@@ -3960,7 +3971,7 @@
}
private void onClosingFinished() {
- mCentralSurfaces.onClosingFinished();
+ mOpenCloseListener.onClosingFinished();
setClosingWithAlphaFadeout(false);
mMediaHierarchyManager.closeGuts();
}
@@ -4519,11 +4530,13 @@
*/
public void initDependencies(
CentralSurfaces centralSurfaces,
+ GestureRecorder recorder,
Runnable hideExpandedRunnable,
NotificationShelfController notificationShelfController) {
// TODO(b/254859580): this can be injected.
mCentralSurfaces = centralSurfaces;
+ mGestureRecorder = recorder;
mHideExpandedRunnable = hideExpandedRunnable;
mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
mNotificationShelfController = notificationShelfController;
@@ -4605,20 +4618,20 @@
return false;
}
- // If the view that would receive the touch is disabled, just have status bar
- // eat the gesture.
- if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) {
- Log.v(TAG,
- String.format(
- "onTouchForwardedFromStatusBar: "
- + "panel view disabled, eating touch at (%d,%d)",
- (int) event.getX(),
- (int) event.getY()
- )
- );
- return true;
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ // If the view that would receive the touch is disabled, just have status
+ // bar eat the gesture.
+ if (!mView.isEnabled()) {
+ mShadeLog.logMotionEvent(event,
+ "onTouchForwardedFromStatusBar: panel view disabled");
+ return true;
+ }
+ if (isFullyCollapsed() && event.getY() < 1f) {
+ // b/235889526 Eat events on the top edge of the phone when collapsed
+ mShadeLog.logMotionEvent(event, "top edge touch ignored");
+ return true;
+ }
}
-
return mView.dispatchTouchEvent(event);
}
};
@@ -5772,7 +5785,7 @@
if (mSplitShadeEnabled && !isOnKeyguard()) {
setQsExpandImmediate(true);
}
- mCentralSurfaces.makeExpandedVisible(false);
+ mOpenCloseListener.onOpenStarted();
}
if (state == STATE_CLOSED) {
setQsExpandImmediate(false);
@@ -6255,4 +6268,17 @@
return super.performAccessibilityAction(host, action, args);
}
}
+
+ /** Listens for when touch tracking begins. */
+ interface TrackingStartedListener {
+ void onTrackingStarted();
+ }
+
+ /** Listens for when shade begins opening of finishes closing. */
+ interface OpenCloseListener {
+ /** Called when the shade finishes closing. */
+ void onClosingFinished();
+ /** Called when the shade starts opening. */
+ void onOpenStarted();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index de9dcf9..a41a15d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -111,6 +111,9 @@
/** Handle status bar touch event. */
void onStatusBarTouch(MotionEvent event);
+ /** Called when the shade finishes collapsing. */
+ void onClosingFinished();
+
/** Sets the listener for when the visibility of the shade changes. */
void setVisibilityListener(ShadeVisibilityListener listener);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 807e2e6..638e748 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -227,6 +227,16 @@
}
@Override
+ public void onClosingFinished() {
+ runPostCollapseRunnables();
+ if (!mPresenter.isPresenterFullyCollapsed()) {
+ // if we set it not to be focusable when collapsing, we have to undo it when we aborted
+ // the closing
+ mNotificationShadeWindowController.setNotificationShadeFocusable(true);
+ }
+ }
+
+ @Override
public void instantCollapseShade() {
mNotificationPanelViewController.instantCollapse();
runPostCollapseRunnables();
@@ -329,5 +339,18 @@
public void setNotificationPanelViewController(
NotificationPanelViewController notificationPanelViewController) {
mNotificationPanelViewController = notificationPanelViewController;
+ mNotificationPanelViewController.setTrackingStartedListener(this::runPostCollapseRunnables);
+ mNotificationPanelViewController.setOpenCloseListener(
+ new NotificationPanelViewController.OpenCloseListener() {
+ @Override
+ public void onClosingFinished() {
+ ShadeControllerImpl.this.onClosingFinished();
+ }
+
+ @Override
+ public void onOpenStarted() {
+ makeExpandedVisible(false);
+ }
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index bc456d5..2334a4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -15,6 +15,7 @@
import android.util.AttributeSet
import android.util.MathUtils.lerp
import android.view.View
+import android.view.animation.PathInterpolator
import com.android.systemui.animation.Interpolators
import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
import com.android.systemui.util.getColorWithAlpha
@@ -88,10 +89,12 @@
class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffect {
- private val INTERPOLATOR = Interpolators.FAST_OUT_SLOW_IN_REVERSE
+ // Interpolator that reveals >80% of the content at 0.5 progress, makes revealing faster
+ private val interpolator = PathInterpolator(/* controlX1= */ 0.4f, /* controlY1= */ 0f,
+ /* controlX2= */ 0.2f, /* controlY2= */ 1f)
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
- val interpolatedAmount = INTERPOLATOR.getInterpolation(amount)
+ val interpolatedAmount = interpolator.getInterpolation(amount)
scrim.interpolatedRevealAmount = interpolatedAmount
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 7eb8906..39daa13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -44,4 +44,8 @@
val shouldFilterUnseenNotifsOnKeyguard: Boolean by lazy {
featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
}
+
+ val isNoHunForOldWhenEnabled: Boolean by lazy {
+ featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index d97b712..3e2dd05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -38,6 +38,7 @@
import javax.inject.Inject
import kotlin.math.min
+
@SysUISingleton
class NotificationWakeUpCoordinator @Inject constructor(
dumpManager: DumpManager,
@@ -45,7 +46,8 @@
private val statusBarStateController: StatusBarStateController,
private val bypassController: KeyguardBypassController,
private val dozeParameters: DozeParameters,
- private val screenOffAnimationController: ScreenOffAnimationController
+ private val screenOffAnimationController: ScreenOffAnimationController,
+ private val logger: NotificationWakeUpCoordinatorLogger,
) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener,
Dumpable {
@@ -242,6 +244,7 @@
}
override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ logger.logOnDozeAmountChanged(linear, eased)
if (overrideDozeAmountIfAnimatingScreenOff(linear)) {
return
}
@@ -273,6 +276,7 @@
}
override fun onStateChanged(newState: Int) {
+ logger.logOnStateChanged(newState)
if (state == StatusBarState.SHADE && newState == StatusBarState.SHADE) {
// The SHADE -> SHADE transition is only possible as part of cancelling the screen-off
// animation (e.g. by fingerprint unlock). This is done because the system is in an
@@ -320,8 +324,12 @@
private fun overrideDozeAmountIfBypass(): Boolean {
if (bypassController.bypassEnabled) {
if (statusBarStateController.state == StatusBarState.KEYGUARD) {
+ logger.logSetDozeAmount("1.0", "1.0",
+ "Override: bypass (keyguard)", StatusBarState.KEYGUARD)
setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)")
} else {
+ logger.logSetDozeAmount("0.0", "0.0",
+ "Override: bypass (shade)", statusBarStateController.state)
setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
}
return true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
new file mode 100644
index 0000000..b40ce25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import javax.inject.Inject
+
+class NotificationWakeUpCoordinatorLogger
+@Inject
+constructor(@NotificationLog private val buffer: LogBuffer) {
+ fun logSetDozeAmount(linear: String, eased: String, source: String, state: Int) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = linear
+ str2 = eased
+ str3 = source
+ int1 = state
+ },
+ { "setDozeAmount: linear: $str1, eased: $str2, source: $str3, state: $int1" }
+ )
+ }
+
+ fun logOnDozeAmountChanged(linear: Float, eased: Float) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ double1 = linear.toDouble()
+ str2 = eased.toString()
+ },
+ { "onDozeAmountChanged($double1, $str2)" }
+ )
+ }
+
+ fun logOnStateChanged(newState: Int) {
+ buffer.log(TAG, DEBUG, { int1 = newState }, { "onStateChanged($int1)" })
+ }
+}
+
+private const val TAG = "NotificationWakeUpCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
index 966ab4c..afdadeb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
@@ -51,7 +51,9 @@
*/
public final void invalidateList(@Nullable String reason) {
if (mListener != null) {
- Trace.beginSection("Pluggable<" + mName + ">.invalidateList");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP, "Pluggable<" + mName + ">.invalidateList");
+ }
mListener.onPluggableInvalidated((This) this, reason);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 073b6b0..13b3aca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -106,6 +106,36 @@
})
}
+ fun logNoHeadsUpOldWhen(
+ entry: NotificationEntry,
+ notifWhen: Long,
+ notifAge: Long
+ ) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
+ long1 = notifWhen
+ long2 = notifAge
+ }, {
+ "No heads up: old when $long1 (age=$long2 ms): $str1"
+ })
+ }
+
+ fun logMaybeHeadsUpDespiteOldWhen(
+ entry: NotificationEntry,
+ notifWhen: Long,
+ notifAge: Long,
+ reason: String
+ ) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
+ str2 = reason
+ long1 = notifWhen
+ long2 = notifAge
+ }, {
+ "Maybe heads up: old when $long1 (age=$long2 ms) but $str2: $str1"
+ })
+ }
+
fun logNoHeadsUpSuppressedBy(
entry: NotificationEntry,
suppressor: NotificationInterruptSuppressor
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index c4f5a3a..ec5bd68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -20,6 +20,7 @@
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR;
+import android.app.Notification;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.database.ContentObserver;
@@ -82,7 +83,10 @@
FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(1235),
@UiEvent(doc = "FSI suppressed for requiring neither HUN nor keyguard")
- FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236);
+ FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236),
+
+ @UiEvent(doc = "HUN suppressed for old when")
+ HUN_SUPPRESSED_OLD_WHEN(1237);
private final int mId;
@@ -346,6 +350,10 @@
return false;
}
+ if (shouldSuppressHeadsUpWhenAwakeForOldWhen(entry, log)) {
+ return false;
+ }
+
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) {
if (log) mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i));
@@ -470,4 +478,51 @@
private boolean isSnoozedPackage(StatusBarNotification sbn) {
return mHeadsUpManager.isSnoozed(sbn.getPackageName());
}
+
+ private boolean shouldSuppressHeadsUpWhenAwakeForOldWhen(NotificationEntry entry, boolean log) {
+ if (!mFlags.isNoHunForOldWhenEnabled()) {
+ return false;
+ }
+
+ final Notification notification = entry.getSbn().getNotification();
+ if (notification == null) {
+ return false;
+ }
+
+ final long when = notification.when;
+ final long now = System.currentTimeMillis();
+ final long age = now - when;
+
+ if (age < MAX_HUN_WHEN_AGE_MS) {
+ return false;
+ }
+
+ if (when <= 0) {
+ // Some notifications (including many system notifications) are posted with the "when"
+ // field set to 0. Nothing in the Javadocs for Notification mentions a special meaning
+ // for a "when" of 0, but Android didn't even exist at the dawn of the Unix epoch.
+ // Therefore, assume that these notifications effectively don't have a "when" value,
+ // and don't suppress HUNs.
+ if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "when <= 0");
+ return false;
+ }
+
+ if (notification.fullScreenIntent != null) {
+ if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "full-screen intent");
+ return false;
+ }
+
+ if (notification.isForegroundService()) {
+ if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "foreground service");
+ return false;
+ }
+
+ if (log) mLogger.logNoHeadsUpOldWhen(entry, when, age);
+ final int uid = entry.getSbn().getUid();
+ final String packageName = entry.getSbn().getPackageName();
+ mUiEventLogger.log(NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN, uid, packageName);
+ return true;
+ }
+
+ public static final long MAX_HUN_WHEN_AGE_MS = 24 * 60 * 60 * 1000;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 1eccc98..d7d5ac9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -19,6 +19,7 @@
import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
import android.animation.Animator;
@@ -1404,6 +1405,11 @@
mKeepInParentForDismissAnimation = keepInParent;
}
+ /** @return true if the User has dismissed this notif's parent */
+ public boolean isParentDismissed() {
+ return getEntry().getDismissState() == PARENT_DISMISSED;
+ }
+
@Override
public boolean isRemoved() {
return mRemoved;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index f9e9a2d..8a400d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -359,10 +359,15 @@
@Override
public boolean offerToKeepInParentForAnimation() {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION)) {
+ //If the User dismissed the notification's parent, we want to keep it attached until the
+ //dismiss animation is ongoing. Therefore we don't want to remove it in the ShadeViewDiffer.
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION)
+ && mView.isParentDismissed()) {
mView.setKeepInParentForDismissAnimation(true);
return true;
}
+
+ //Otherwise the view system doesn't do the removal, so we rely on the ShadeViewDiffer
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 679bcea..5564311 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -514,7 +514,7 @@
&& mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser();
}
- public int getMode() {
+ public @WakeAndUnlockMode int getMode() {
return mMode;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 883ce1e..c6f64f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -51,7 +51,6 @@
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
-import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.LightRevealScrim;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -286,8 +285,6 @@
void onTouchEvent(MotionEvent event);
- GestureRecorder getGestureRecorder();
-
BiometricUnlockController getBiometricUnlockController();
void showWirelessChargingAnimation(int batteryLevel);
@@ -402,10 +399,6 @@
LightRevealScrim getLightRevealScrim();
- void onTrackingStarted();
-
- void onClosingFinished();
-
// TODO: Figure out way to remove these.
NavigationBarView getNavigationBarView();
@@ -489,13 +482,6 @@
void updateNotificationPanelTouchState();
- /**
- * TODO(b/257041702) delete this
- * @deprecated Use ShadeController#makeExpandedVisible
- */
- @Deprecated
- void makeExpandedVisible(boolean force);
-
int getDisplayId();
int getRotation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 1c0febb..2d48ffc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -160,6 +160,8 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.plugins.DarkIconDispatcher;
@@ -491,6 +493,7 @@
private final OngoingCallController mOngoingCallController;
private final StatusBarSignalPolicy mStatusBarSignalPolicy;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
+ private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy;
/** Controller for the Shade. */
@VisibleForTesting
@@ -757,7 +760,8 @@
DeviceStateManager deviceStateManager,
WiredChargingRippleController wiredChargingRippleController,
IDreamManager dreamManager,
- Lazy<CameraLauncher> cameraLauncherLazy) {
+ Lazy<CameraLauncher> cameraLauncherLazy,
+ Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy) {
mContext = context;
mNotificationsController = notificationsController;
mFragmentService = fragmentService;
@@ -871,6 +875,8 @@
deviceStateManager.registerCallback(mMainExecutor,
new FoldStateListener(mContext, this::onFoldedStateChanged));
wiredChargingRippleController.registerCallbacks();
+
+ mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
}
@Override
@@ -1000,6 +1006,12 @@
@Override
public void onKeyguardGoingAwayChanged() {
+ if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+ // This code path is not used if the KeyguardTransitionRepository is managing
+ // the lightreveal scrim.
+ return;
+ }
+
// The light reveal scrim should always be fully revealed by the time the keyguard
// is done going away. Double check that this is true.
if (!mKeyguardStateController.isKeyguardGoingAway()) {
@@ -1236,6 +1248,12 @@
mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront);
mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
+
+ if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+ LightRevealScrimViewBinder.bind(
+ mLightRevealScrim, mLightRevealScrimViewModelLazy.get());
+ }
+
mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> {
Runnable updateOpaqueness = () -> {
mNotificationShadeWindowController.setLightRevealScrimOpaque(
@@ -1257,6 +1275,7 @@
mNotificationPanelViewController.initDependencies(
this,
+ mGestureRec,
mShadeController::makeExpandedInvisible,
mNotificationShelfController);
@@ -1855,7 +1874,7 @@
public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
if (mPresenter.isPresenterFullyCollapsed() && !mPresenter.isCollapsing()
&& isLaunchForActivity) {
- onClosingFinished();
+ mShadeController.onClosingFinished();
} else {
mShadeController.collapseShade(true /* animate */);
}
@@ -1865,7 +1884,7 @@
@Override
public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
if (!mPresenter.isCollapsing()) {
- onClosingFinished();
+ mShadeController.onClosingFinished();
}
if (launchIsFullScreen) {
mShadeController.instantCollapseShade();
@@ -2052,11 +2071,6 @@
}
@Override
- public GestureRecorder getGestureRecorder() {
- return mGestureRec;
- }
-
- @Override
public BiometricUnlockController getBiometricUnlockController() {
return mBiometricUnlockController;
}
@@ -3312,6 +3326,10 @@
return;
}
+ if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+ return;
+ }
+
final boolean wakingUpFromPowerButton = wakingUp
&& !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
&& mWakefulnessLifecycle.getLastWakeReason()
@@ -3338,21 +3356,6 @@
return mLightRevealScrim;
}
- @Override
- public void onTrackingStarted() {
- mShadeController.runPostCollapseRunnables();
- }
-
- @Override
- public void onClosingFinished() {
- mShadeController.runPostCollapseRunnables();
- if (!mPresenter.isPresenterFullyCollapsed()) {
- // if we set it not to be focusable when collapsing, we have to undo it when we aborted
- // the closing
- mNotificationShadeWindowController.setNotificationShadeFocusable(true);
- }
- }
-
// TODO: Figure out way to remove these.
@Override
public NavigationBarView getNavigationBarView() {
@@ -3584,12 +3587,6 @@
mNotificationIconAreaController.setAnimationsEnabled(!disabled);
}
- //TODO(b/257041702) delete
- @Override
- public void makeExpandedVisible(boolean force) {
- mShadeController.makeExpandedVisible(force);
- }
-
final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurningOn(Runnable onDrawn) {
@@ -4097,7 +4094,9 @@
return;
}
- mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
+ if (!mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+ mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
+ }
}
@Override
@@ -4278,6 +4277,7 @@
@Override
public void onDozeAmountChanged(float linear, float eased) {
if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
+ && !mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)
&& !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
mLightRevealScrim.setRevealAmount(1f - linear);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index aa0757e..000fe14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -240,8 +240,8 @@
&& !mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
KeyguardUpdateMonitor.getCurrentUser())
&& !needsFullscreenBouncer()
- && !mKeyguardUpdateMonitor.isFaceLockedOut()
- && !mKeyguardUpdateMonitor.userNeedsStrongAuth()
+ && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE)
&& !mKeyguardBypassController.getBypassEnabled()) {
mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index fb0d3e4..d500f99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -352,6 +352,11 @@
.getBoolean(R.bool.notification_scrim_transparent);
updateScrims();
mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
+
+ // prepare() sets proper initial values for most states
+ for (ScrimState state : ScrimState.values()) {
+ state.prepare(state);
+ }
}
/**
@@ -641,10 +646,6 @@
private void setTransitionToFullShade(boolean transitioning) {
if (transitioning != mTransitioningToFullShade) {
mTransitioningToFullShade = transitioning;
- if (transitioning) {
- // Let's make sure the shade locked is ready
- ScrimState.SHADE_LOCKED.prepare(mState);
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 52430d3..0e9d3ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -146,18 +146,12 @@
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
mNotifAlpha = 1f;
mFrontAlpha = 0f;
- mBehindTint = Color.BLACK;
+ mBehindTint = mClipQsScrim ? Color.TRANSPARENT : Color.BLACK;
if (mClipQsScrim) {
updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
}
}
-
- // to make sure correct color is returned before "prepare" is called
- @Override
- public int getBehindTint() {
- return Color.BLACK;
- }
},
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 946d7e4..4d914fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -52,6 +52,6 @@
* Returns true if we should apply some coloring to the wifi icon that was rendered with the new
* pipeline to help with debugging.
*/
- // For now, just always apply the debug coloring if we've enabled the new icon.
- fun useWifiDebugColoring(): Boolean = useNewWifiIcon()
+ fun useWifiDebugColoring(): Boolean =
+ featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
index fe30c01..4a5342e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
@@ -36,16 +36,20 @@
* [com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository] for
* more details.
*/
+interface AirplaneModeViewModel {
+ /** True if the airplane mode icon is currently visible in the status bar. */
+ val isAirplaneModeIconVisible: StateFlow<Boolean>
+}
+
@SysUISingleton
-class AirplaneModeViewModel
+class AirplaneModeViewModelImpl
@Inject
constructor(
interactor: AirplaneModeInteractor,
logger: ConnectivityPipelineLogger,
@Application private val scope: CoroutineScope,
-) {
- /** True if the airplane mode icon is currently visible in the status bar. */
- val isAirplaneModeIconVisible: StateFlow<Boolean> =
+) : AirplaneModeViewModel {
+ override val isAirplaneModeIconVisible: StateFlow<Boolean> =
combine(interactor.isAirplaneMode, interactor.isForceHidden) {
isAirplaneMode,
isAirplaneIconForceHidden ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index c961422..fb67f1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -21,6 +21,8 @@
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
@@ -33,6 +35,8 @@
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -43,12 +47,18 @@
abstract fun airplaneModeRepository(impl: AirplaneModeRepositoryImpl): AirplaneModeRepository
@Binds
+ abstract fun airplaneModeViewModel(impl: AirplaneModeViewModelImpl): AirplaneModeViewModel
+
+ @Binds
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
@Binds
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
@Binds
+ abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
+
+ @Binds
abstract fun mobileConnectionsRepository(
impl: MobileConnectionsRepositoryImpl
): MobileConnectionsRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 3a3e611..ec935fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -34,16 +34,36 @@
* This interactor processes information from our data layer into information that the UI layer can
* use.
*/
-@SysUISingleton
-class WifiInteractor @Inject constructor(
- connectivityRepository: ConnectivityRepository,
- wifiRepository: WifiRepository,
-) {
+interface WifiInteractor {
/**
* The SSID (service set identifier) of the wifi network. Null if we don't have a network, or
* have a network but no valid SSID.
*/
- val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
+ val ssid: Flow<String?>
+
+ /** Our current enabled status. */
+ val isEnabled: Flow<Boolean>
+
+ /** Our current default status. */
+ val isDefault: Flow<Boolean>
+
+ /** Our current wifi network. See [WifiNetworkModel]. */
+ val wifiNetwork: Flow<WifiNetworkModel>
+
+ /** Our current wifi activity. See [WifiActivityModel]. */
+ val activity: StateFlow<WifiActivityModel>
+
+ /** True if we're configured to force-hide the wifi icon and false otherwise. */
+ val isForceHidden: Flow<Boolean>
+}
+
+@SysUISingleton
+class WifiInteractorImpl @Inject constructor(
+ connectivityRepository: ConnectivityRepository,
+ wifiRepository: WifiRepository,
+) : WifiInteractor {
+
+ override val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
when (info) {
is WifiNetworkModel.Inactive -> null
is WifiNetworkModel.CarrierMerged -> null
@@ -56,20 +76,15 @@
}
}
- /** Our current enabled status. */
- val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
+ override val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
- /** Our current default status. */
- val isDefault: Flow<Boolean> = wifiRepository.isWifiDefault
+ override val isDefault: Flow<Boolean> = wifiRepository.isWifiDefault
- /** Our current wifi network. See [WifiNetworkModel]. */
- val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
+ override val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
- /** Our current wifi activity. See [WifiActivityModel]. */
- val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
+ override val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
- /** True if we're configured to force-hide the wifi icon and false otherwise. */
- val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
+ override val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
it.contains(ConnectivitySlot.WIFI)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index 0c72b78..2b29885 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.user;
import android.app.Activity;
+import android.os.UserHandle;
import com.android.settingslib.users.EditUserInfoController;
import com.android.systemui.user.data.repository.UserRepositoryModule;
@@ -51,4 +52,22 @@
@IntoMap
@ClassKey(UserSwitcherActivity.class)
public abstract Activity provideUserSwitcherActivity(UserSwitcherActivity activity);
+
+ /**
+ * Provides the {@link UserHandle} for the user associated with this System UI process.
+ *
+ * <p>Note that this is static and unchanging for the life-time of the process we are running
+ * in. It can be <i>different</i> from the user that is the currently-selected user, which may
+ * be associated with a different System UI process.
+ *
+ * <p>For example, the System UI process which creates all the windows and renders UI is always
+ * the one associated with the primary user on the device. However, if the user is switched to
+ * another, non-primary user (for example user "X"), then a secondary System UI process will be
+ * spawned. While the original primary user process continues to be the only one rendering UI,
+ * the new system UI process may be used for things like file or content access.
+ */
+ @Provides
+ public static UserHandle provideUserHandle() {
+ return new UserHandle(UserHandle.myUserId());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index c5b697c..d7b0971 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -114,9 +114,9 @@
private val callbackMutex = Mutex()
private val callbacks = mutableSetOf<UserCallback>()
- private val userInfos =
- combine(repository.userSwitcherSettings, repository.userInfos) { settings, userInfos ->
- userInfos.filter { !it.isGuest || canCreateGuestUser(settings) }.filter { it.isFull }
+ private val userInfos: Flow<List<UserInfo>> =
+ repository.userInfos.map { userInfos ->
+ userInfos.filter { it.isFull }
}
/** List of current on-device users to select from. */
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 0910ea3..37115ad 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -19,6 +19,8 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.ui.drawable.CircularDrawable
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.GuestUserInteractor
@@ -144,7 +146,12 @@
): UserViewModel {
return UserViewModel(
viewKey = model.id,
- name = model.name,
+ name =
+ if (model.isGuest && model.isSelected) {
+ Text.Resource(R.string.guest_exit_quick_settings_button)
+ } else {
+ model.name
+ },
image = CircularDrawable(model.image),
isSelectionMarkerVisible = model.isSelected,
alpha =
diff --git a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
index 5b16ae9..b311318 100644
--- a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
@@ -22,11 +22,22 @@
* Run a block within a [Trace] section.
* Calls [Trace.beginSection] before and [Trace.endSection] after the passed block.
*/
-inline fun <T> traceSection(tag: String, block: () -> T): T {
- Trace.beginSection(tag)
- try {
- return block()
- } finally {
- Trace.endSection()
+inline fun <T> traceSection(tag: String, block: () -> T): T =
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
+ try {
+ block()
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_APP)
+ }
+ } else {
+ block()
+ }
+
+class TraceUtils {
+ companion object {
+ inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable {
+ return Runnable { traceSection(tag) { block() } }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 8839662..afd582a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -63,7 +63,6 @@
credentialAttempted = false,
deviceInteractive = false,
dreaming = false,
- encryptedOrLockdown = false,
fingerprintDisabled = false,
fingerprintLockedOut = false,
goingToSleep = false,
@@ -74,6 +73,7 @@
primaryUser = false,
shouldListenSfpsState = false,
shouldListenForFingerprintAssistant = false,
+ strongerAuthRequired = false,
switchingUser = false,
udfps = false,
userDoesNotHaveTrust = false
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 4d58b09..84f6d91 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -379,9 +379,9 @@
}
@Test
- public void onBouncerVisibilityChanged_needsStrongAuth_sideFpsHintHidden() {
+ public void onBouncerVisibilityChanged_unlockingWithFingerprintNotAllowed_sideFpsHintHidden() {
setupConditionsToEnableSideFpsHint();
- setNeedsStrongAuth(true);
+ setUnlockingWithFingerprintAllowed(false);
reset(mSideFpsController);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
@@ -558,11 +558,40 @@
configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged();
verify(mView).onDensityOrFontScaleChanged();
- verify(mKeyguardSecurityViewFlipperController).onDensityOrFontScaleChanged();
+ verify(mKeyguardSecurityViewFlipperController).clearViews();
verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
any(KeyguardSecurityCallback.class));
}
+ @Test
+ public void onThemeChanged() {
+ ArgumentCaptor<ConfigurationController.ConfigurationListener>
+ configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
+ ConfigurationController.ConfigurationListener.class);
+ mKeyguardSecurityContainerController.onViewAttached();
+ verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
+ configurationListenerArgumentCaptor.getValue().onThemeChanged();
+
+ verify(mView).reloadColors();
+ verify(mKeyguardSecurityViewFlipperController).clearViews();
+ verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
+ any(KeyguardSecurityCallback.class));
+ }
+
+ @Test
+ public void onUiModeChanged() {
+ ArgumentCaptor<ConfigurationController.ConfigurationListener>
+ configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
+ ConfigurationController.ConfigurationListener.class);
+ mKeyguardSecurityContainerController.onViewAttached();
+ verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
+ configurationListenerArgumentCaptor.getValue().onUiModeChanged();
+
+ verify(mView).reloadColors();
+ verify(mKeyguardSecurityViewFlipperController).clearViews();
+ verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
+ any(KeyguardSecurityCallback.class));
+ }
private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
mKeyguardSecurityContainerController.onViewAttached();
@@ -574,7 +603,7 @@
attachView();
setSideFpsHintEnabledFromResources(true);
setFingerprintDetectionRunning(true);
- setNeedsStrongAuth(false);
+ setUnlockingWithFingerprintAllowed(true);
}
private void attachView() {
@@ -593,9 +622,8 @@
enabled);
}
- private void setNeedsStrongAuth(boolean needed) {
- when(mKeyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(needed);
- mKeyguardUpdateMonitorCallback.getValue().onStrongAuthStateChanged(/* userId= */ 0);
+ private void setUnlockingWithFingerprintAllowed(boolean allowed) {
+ when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(allowed);
}
private void setupGetSecurityView() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index fd02ac9..1614b57 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -109,7 +109,7 @@
@Test
public void onDensityOrFontScaleChanged() {
- mKeyguardSecurityViewFlipperController.onDensityOrFontScaleChanged();
+ mKeyguardSecurityViewFlipperController.clearViews();
verify(mView).removeAllViews();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index beb9a72..c87a664 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -27,6 +27,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
@@ -281,7 +282,6 @@
componentInfo, FaceSensorProperties.TYPE_UNKNOWN,
false /* supportsFaceDetection */, true /* supportsSelfIllumination */,
false /* resetLockoutRequiresChallenge */));
-
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of(
@@ -594,30 +594,13 @@
}
@Test
- public void testFingerprintDoesNotAuth_whenEncrypted() {
- testFingerprintWhenStrongAuth(
- STRONG_AUTH_REQUIRED_AFTER_BOOT);
- }
-
- @Test
- public void testFingerprintDoesNotAuth_whenDpmLocked() {
- testFingerprintWhenStrongAuth(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW);
- }
-
- @Test
- public void testFingerprintDoesNotAuth_whenUserLockdown() {
- testFingerprintWhenStrongAuth(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- }
-
- private void testFingerprintWhenStrongAuth(int strongAuth) {
+ public void testOnlyDetectFingerprint_whenFingerprintUnlockNotAllowed() {
// Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
// will trigger updateBiometricListeningState();
clearInvocations(mFingerprintManager);
mKeyguardUpdateMonitor.resetBiometricListeningState();
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
mTestableLooper.processAllMessages();
@@ -626,6 +609,64 @@
}
@Test
+ public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() {
+ // GIVEN unlocking with biometric is allowed
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+
+ // THEN unlocking with face and fp is allowed
+ Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE));
+ Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT));
+ }
+
+ @Test
+ public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() {
+ // GIVEN unlocking with biometric is not allowed
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+
+ // THEN unlocking with face is not allowed
+ Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE));
+ }
+
+ @Test
+ public void testUnlockingWithFaceAllowed_fingerprintLockout() {
+ // GIVEN unlocking with biometric is allowed
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+
+ // WHEN fingerprint is locked out
+ fingerprintErrorLockedOut();
+
+ // THEN unlocking with face is not allowed
+ Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE));
+ }
+
+ @Test
+ public void testUnlockingWithFpAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() {
+ // GIVEN unlocking with biometric is not allowed
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+
+ // THEN unlocking with fingerprint is not allowed
+ Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT));
+ }
+
+ @Test
+ public void testUnlockingWithFpAllowed_fingerprintLockout() {
+ // GIVEN unlocking with biometric is allowed
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+
+ // WHEN fingerprint is locked out
+ fingerprintErrorLockedOut();
+
+ // THEN unlocking with fingeprint is not allowed
+ Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT));
+ }
+
+ @Test
public void testTriesToAuthenticate_whenBouncer() {
setKeyguardBouncerVisibility(true);
@@ -928,10 +969,6 @@
faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
final boolean fpLocked =
fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
- when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
- .thenReturn(fingerprintLockoutMode);
- when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
- .thenReturn(faceLockoutMode);
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
@@ -940,7 +977,13 @@
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
anyInt());
+// resetFaceManager();
+// resetFingerprintManager();
+ when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
+ .thenReturn(fingerprintLockoutMode);
+ when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
+ .thenReturn(faceLockoutMode);
final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
@@ -951,14 +994,22 @@
mKeyguardUpdateMonitor.handleUserSwitchComplete(newUser);
mTestableLooper.processAllMessages();
- verify(faceCancel, faceLocked ? times(1) : never()).cancel();
- verify(fpCancel, fpLocked ? times(1) : never()).cancel();
- verify(callback, faceLocked ? times(1) : never()).onBiometricRunningStateChanged(
+ // THEN face and fingerprint listening are always cancelled immediately
+ verify(faceCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
eq(false), eq(BiometricSourceType.FACE));
- verify(callback, fpLocked ? times(1) : never()).onBiometricRunningStateChanged(
+ verify(fpCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
eq(false), eq(BiometricSourceType.FINGERPRINT));
+
+ // THEN locked out states are updated
assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked);
assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked);
+
+ // Fingerprint should be restarted once its cancelled bc on lockout, the device
+ // can still detectFingerprint (and if it's not locked out, fingerprint can listen)
+ assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
+ .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
}
@Test
@@ -1144,9 +1195,8 @@
// GIVEN status bar state is on the keyguard
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
- // WHEN user hasn't authenticated since last boot
- when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
- .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ // WHEN user hasn't authenticated since last boot, cannot unlock with FP
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
// THEN we shouldn't listen for udfps
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1259,8 +1309,7 @@
when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
// WHEN device in lock down
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
// THEN we shouldn't listen for udfps
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1294,6 +1343,24 @@
}
@Test
+ public void testRequestFaceAuthFromOccludingApp_whenInvoked_startsFaceAuth() {
+ mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
+
+ assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isTrue();
+ }
+
+ @Test
+ public void testRequestFaceAuthFromOccludingApp_whenInvoked_stopsFaceAuth() {
+ mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
+
+ assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isTrue();
+
+ mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
+
+ assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse();
+ }
+
+ @Test
public void testRequireUnlockForNfc_Broadcast() {
KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
mKeyguardUpdateMonitor.registerCallback(callback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 0b528a5..b765ab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -26,6 +26,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.LightRevealScrim
@@ -37,7 +38,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.leak.RotationUtils
-import javax.inject.Provider
+import com.android.systemui.util.mockito.any
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -46,15 +47,16 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
-import org.mockito.Mockito.any
+import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
+import javax.inject.Provider
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -76,6 +78,7 @@
@Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
@Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var lightRevealScrim: LightRevealScrim
@Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
@@ -105,6 +108,7 @@
biometricUnlockController,
udfpsControllerProvider,
statusBarStateController,
+ featureFlags,
rippleView
)
controller.init()
@@ -118,12 +122,13 @@
@Test
fun testFingerprintTrigger_KeyguardShowing_Ripple() {
- // GIVEN fp exists, keyguard is showing, user doesn't need strong auth
+ // GIVEN fp exists, keyguard is showing, unlocking with fp allowed
val fpsLocation = Point(5, 5)
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
// WHEN fingerprint authenticated
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
@@ -140,11 +145,12 @@
@Test
fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() {
- // GIVEN fp exists & user doesn't need strong auth
+ // GIVEN fp exists & unlocking with fp allowed
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
// WHEN keyguard is NOT showing & fingerprint authenticated
`when`(keyguardStateController.isShowing).thenReturn(false)
@@ -160,15 +166,16 @@
}
@Test
- fun testFingerprintTrigger_StrongAuthRequired_NoRipple() {
+ fun testFingerprintTrigger_biometricUnlockNotAllowed_NoRipple() {
// GIVEN fp exists & keyguard is showing
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
- // WHEN user needs strong auth & fingerprint authenticated
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(true)
+ // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ eq(BiometricSourceType.FINGERPRINT))).thenReturn(false)
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
captor.value.onBiometricAuthenticated(
@@ -182,13 +189,14 @@
@Test
fun testFaceTriggerBypassEnabled_Ripple() {
- // GIVEN face auth sensor exists, keyguard is showing & strong auth isn't required
+ // GIVEN face auth sensor exists, keyguard is showing & unlocking with face is allowed
val faceLocation = Point(5, 5)
`when`(authController.faceSensorLocation).thenReturn(faceLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE)).thenReturn(true)
// WHEN bypass is enabled & face authenticated
`when`(bypassController.canBypass()).thenReturn(true)
@@ -275,6 +283,8 @@
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT)).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
@@ -295,6 +305,8 @@
`when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
`when`(authController.isUdfpsFingerDown).thenReturn(true)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ eq(BiometricSourceType.FACE))).thenReturn(true)
controller.showUnlockRipple(BiometricSourceType.FACE)
assertTrue("reveal didn't start on keyguardFadingAway",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt
new file mode 100644
index 0000000..4b88b44
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls
+
+import android.content.pm.UserInfo
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class ControlsSettingsRepositoryImplTest : SysuiTestCase() {
+
+ companion object {
+ private const val LOCKSCREEN_SHOW = Settings.Secure.LOCKSCREEN_SHOW_CONTROLS
+ private const val LOCKSCREEN_ACTION = Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS
+
+ private fun createUser(id: Int): UserInfo {
+ return UserInfo(id, "user_$id", 0)
+ }
+
+ private val ALL_USERS = (0..1).map { it to createUser(it) }.toMap()
+ }
+
+ private lateinit var underTest: ControlsSettingsRepository
+
+ private lateinit var testScope: TestScope
+ private lateinit var secureSettings: FakeSettings
+ private lateinit var userRepository: FakeUserRepository
+
+ @Before
+ fun setUp() {
+ secureSettings = FakeSettings()
+ userRepository = FakeUserRepository()
+ userRepository.setUserInfos(ALL_USERS.values.toList())
+
+ val coroutineDispatcher = UnconfinedTestDispatcher()
+ testScope = TestScope(coroutineDispatcher)
+
+ underTest =
+ ControlsSettingsRepositoryImpl(
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = coroutineDispatcher,
+ userRepository = userRepository,
+ secureSettings = secureSettings,
+ )
+ }
+
+ @Test
+ fun showInLockScreen() =
+ testScope.runTest {
+ setUser(0)
+ val values = mutableListOf<Boolean>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.canShowControlsInLockscreen.toList(values)
+ }
+ assertThat(values.last()).isFalse()
+
+ secureSettings.putBool(LOCKSCREEN_SHOW, true)
+ assertThat(values.last()).isTrue()
+
+ secureSettings.putBool(LOCKSCREEN_SHOW, false)
+ assertThat(values.last()).isFalse()
+
+ secureSettings.putBoolForUser(LOCKSCREEN_SHOW, true, 1)
+ assertThat(values.last()).isFalse()
+
+ setUser(1)
+ assertThat(values.last()).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun showInLockScreen_changesInOtherUsersAreNotQueued() =
+ testScope.runTest {
+ setUser(0)
+
+ val values = mutableListOf<Boolean>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.canShowControlsInLockscreen.toList(values)
+ }
+
+ secureSettings.putBoolForUser(LOCKSCREEN_SHOW, true, 1)
+ secureSettings.putBoolForUser(LOCKSCREEN_SHOW, false, 1)
+
+ setUser(1)
+ assertThat(values.last()).isFalse()
+ assertThat(values).containsNoneIn(listOf(true))
+
+ job.cancel()
+ }
+
+ @Test
+ fun actionInLockScreen() =
+ testScope.runTest {
+ setUser(0)
+ val values = mutableListOf<Boolean>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.allowActionOnTrivialControlsInLockscreen.toList(values)
+ }
+ assertThat(values.last()).isFalse()
+
+ secureSettings.putBool(LOCKSCREEN_ACTION, true)
+ assertThat(values.last()).isTrue()
+
+ secureSettings.putBool(LOCKSCREEN_ACTION, false)
+ assertThat(values.last()).isFalse()
+
+ secureSettings.putBoolForUser(LOCKSCREEN_ACTION, true, 1)
+ assertThat(values.last()).isFalse()
+
+ setUser(1)
+ assertThat(values.last()).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun actionInLockScreen_changesInOtherUsersAreNotQueued() =
+ testScope.runTest {
+ setUser(0)
+
+ val values = mutableListOf<Boolean>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.allowActionOnTrivialControlsInLockscreen.toList(values)
+ }
+
+ secureSettings.putBoolForUser(LOCKSCREEN_ACTION, true, 1)
+ secureSettings.putBoolForUser(LOCKSCREEN_ACTION, false, 1)
+
+ setUser(1)
+ assertThat(values.last()).isFalse()
+ assertThat(values).containsNoneIn(listOf(true))
+
+ job.cancel()
+ }
+
+ @Test
+ fun valueIsUpdatedWhenNotSubscribed() =
+ testScope.runTest {
+ setUser(0)
+ assertThat(underTest.canShowControlsInLockscreen.value).isFalse()
+
+ secureSettings.putBool(LOCKSCREEN_SHOW, true)
+
+ assertThat(underTest.canShowControlsInLockscreen.value).isTrue()
+ }
+
+ private suspend fun setUser(id: Int) {
+ secureSettings.userId = id
+ userRepository.setSelectedUserInfo(ALL_USERS[id]!!)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt
new file mode 100644
index 0000000..8a1bed2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeControlsSettingsRepository : ControlsSettingsRepository {
+ private val _canShowControlsInLockscreen = MutableStateFlow(false)
+ override val canShowControlsInLockscreen = _canShowControlsInLockscreen.asStateFlow()
+ private val _allowActionOnTrivialControlsInLockscreen = MutableStateFlow(false)
+ override val allowActionOnTrivialControlsInLockscreen =
+ _allowActionOnTrivialControlsInLockscreen.asStateFlow()
+
+ fun setCanShowControlsInLockscreen(value: Boolean) {
+ _canShowControlsInLockscreen.value = value
+ }
+
+ fun setAllowActionOnTrivialControlsInLockscreen(value: Boolean) {
+ _allowActionOnTrivialControlsInLockscreen.value = value
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 4ed5649c..1d00d6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -18,30 +18,24 @@
import android.content.Context
import android.content.SharedPreferences
-import android.database.ContentObserver
-import android.net.Uri
-import android.os.Handler
-import android.os.UserHandle
-import android.provider.Settings
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.FakeControlsSettingsRepository
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.settings.SecureSettings
import com.android.wm.shell.TaskViewFactory
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when`
@@ -79,8 +73,6 @@
@Mock
private lateinit var secureSettings: SecureSettings
@Mock
- private lateinit var mainHandler: Handler
- @Mock
private lateinit var userContextProvider: UserContextProvider
companion object {
@@ -91,17 +83,15 @@
private lateinit var coordinator: ControlActionCoordinatorImpl
private lateinit var action: ControlActionCoordinatorImpl.Action
+ private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(secureSettings.getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS))
- .thenReturn(Settings.Secure
- .getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS))
- `when`(secureSettings.getIntForUser(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
- 0, UserHandle.USER_CURRENT))
- .thenReturn(1)
+ controlsSettingsRepository = FakeControlsSettingsRepository()
+ controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+ controlsSettingsRepository.setCanShowControlsInLockscreen(true)
coordinator = spy(ControlActionCoordinatorImpl(
mContext,
@@ -115,7 +105,7 @@
vibratorHelper,
secureSettings,
userContextProvider,
- mainHandler
+ controlsSettingsRepository
))
val userContext = mock(Context::class.java)
@@ -128,9 +118,6 @@
`when`(pref.getInt(DeviceControlsControllerImpl.PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
.thenReturn(2)
- verify(secureSettings).registerContentObserverForUser(any(Uri::class.java),
- anyBoolean(), any(ContentObserver::class.java), anyInt())
-
`when`(cvh.cws.ci.controlId).thenReturn(ID)
`when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
action = spy(coordinator.Action(ID, {}, false, true))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 77f451f..48fc46b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -17,19 +17,18 @@
package com.android.systemui.controls.dagger
import android.testing.AndroidTestingRunner
-import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.FakeControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.settings.SecureSettings
import dagger.Lazy
import java.util.Optional
import org.junit.Assert.assertEquals
@@ -63,13 +62,13 @@
@Mock
private lateinit var lockPatternUtils: LockPatternUtils
@Mock
- private lateinit var secureSettings: SecureSettings
- @Mock
private lateinit var optionalControlsTileResourceConfiguration:
Optional<ControlsTileResourceConfiguration>
@Mock
private lateinit var controlsTileResourceConfiguration: ControlsTileResourceConfiguration
+ private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
+
companion object {
fun <T> eq(value: T): T = Mockito.eq(value) ?: value
}
@@ -78,6 +77,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ controlsSettingsRepository = FakeControlsSettingsRepository()
+
`when`(userTracker.userHandle.identifier).thenReturn(0)
`when`(optionalControlsTileResourceConfiguration.orElse(any()))
.thenReturn(controlsTileResourceConfiguration)
@@ -125,8 +126,7 @@
`when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(STRONG_AUTH_NOT_REQUIRED)
`when`(keyguardStateController.isUnlocked()).thenReturn(false)
- `when`(secureSettings.getInt(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), anyInt()))
- .thenReturn(0)
+ controlsSettingsRepository.setCanShowControlsInLockscreen(false)
val component = setupComponent(true)
assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility())
@@ -137,9 +137,7 @@
`when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(STRONG_AUTH_NOT_REQUIRED)
`when`(keyguardStateController.isUnlocked()).thenReturn(false)
- `when`(secureSettings.getIntForUser(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS),
- anyInt(), anyInt()))
- .thenReturn(1)
+ controlsSettingsRepository.setCanShowControlsInLockscreen(true)
val component = setupComponent(true)
assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility())
@@ -147,8 +145,7 @@
@Test
fun testFeatureEnabledAndCanShowWhileUnlockedVisibility() {
- `when`(secureSettings.getInt(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), anyInt()))
- .thenReturn(0)
+ controlsSettingsRepository.setCanShowControlsInLockscreen(false)
`when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(STRONG_AUTH_NOT_REQUIRED)
`when`(keyguardStateController.isUnlocked()).thenReturn(true)
@@ -187,7 +184,7 @@
lockPatternUtils,
keyguardStateController,
userTracker,
- secureSettings,
+ controlsSettingsRepository,
optionalControlsTileResourceConfiguration
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
index 32c5b3f..cef452b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -20,6 +20,7 @@
import android.content.ContentValues
import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SystemUIAppComponentFactoryBase
@@ -27,8 +28,10 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -74,8 +77,8 @@
underTest = KeyguardQuickAffordanceProvider()
val scope = CoroutineScope(IMMEDIATE)
- val selectionManager =
- KeyguardQuickAffordanceSelectionManager(
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager =
mock<UserFileManager>().apply {
@@ -91,11 +94,20 @@
userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
scope = scope,
- selectionManager = selectionManager,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
configs =
setOf(
FakeKeyguardQuickAffordanceConfig(
@@ -114,9 +126,10 @@
scope = scope,
backgroundDispatcher = IMMEDIATE,
secureSettings = FakeSettings(),
- selectionsManager = selectionManager,
+ selectionsManager = localUserSelectionManager,
),
dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
underTest.interactor =
KeyguardQuickAffordanceInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
index cda7018..9fa7db1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.statusbar.policy.FlashlightController
import com.android.systemui.utils.leaks.FakeFlashlightController
import com.android.systemui.utils.leaks.LeakCheckedTest
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -38,156 +39,177 @@
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
@Mock private lateinit var context: Context
private lateinit var flashlightController: FakeFlashlightController
- private lateinit var underTest : FlashlightQuickAffordanceConfig
+ private lateinit var underTest: FlashlightQuickAffordanceConfig
@Before
fun setUp() {
injectLeakCheckedDependency(FlashlightController::class.java)
MockitoAnnotations.initMocks(this)
- flashlightController = SysuiLeakCheck().getLeakChecker(FlashlightController::class.java) as FakeFlashlightController
+ flashlightController =
+ SysuiLeakCheck().getLeakChecker(FlashlightController::class.java)
+ as FakeFlashlightController
underTest = FlashlightQuickAffordanceConfig(context, flashlightController)
}
@Test
fun `flashlight is off -- triggered -- icon is on and active`() = runTest {
- //given
+ // given
flashlightController.isEnabled = false
flashlightController.isAvailable = true
val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
- val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
- //when
+ // when
underTest.onTriggered(null)
val lastValue = values.last()
- //then
+ // then
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
- assertEquals(R.drawable.ic_flashlight_on,
- ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
+ assertEquals(
+ R.drawable.qs_flashlight_icon_on,
+ ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
+ as? Icon.Resource)
+ ?.res
+ )
job.cancel()
}
@Test
fun `flashlight is on -- triggered -- icon is off and inactive`() = runTest {
- //given
+ // given
flashlightController.isEnabled = true
flashlightController.isAvailable = true
val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
- val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
- //when
+ // when
underTest.onTriggered(null)
val lastValue = values.last()
- //then
+ // then
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
- assertEquals(R.drawable.ic_flashlight_off,
- ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
+ assertEquals(
+ R.drawable.qs_flashlight_icon_off,
+ ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
+ as? Icon.Resource)
+ ?.res
+ )
job.cancel()
}
@Test
fun `flashlight is on -- receives error -- icon is off and inactive`() = runTest {
- //given
+ // given
flashlightController.isEnabled = true
flashlightController.isAvailable = false
val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
- val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
- //when
+ // when
flashlightController.onFlashlightError()
val lastValue = values.last()
- //then
+ // then
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
- assertEquals(R.drawable.ic_flashlight_off,
- ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
+ assertEquals(
+ R.drawable.qs_flashlight_icon_off,
+ ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
+ as? Icon.Resource)
+ ?.res
+ )
job.cancel()
}
@Test
fun `flashlight availability now off -- hidden`() = runTest {
- //given
+ // given
flashlightController.isEnabled = true
flashlightController.isAvailable = false
val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
- val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
- //when
+ // when
flashlightController.onFlashlightAvailabilityChanged(false)
val lastValue = values.last()
- //then
+ // then
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@Test
fun `flashlight availability now on -- flashlight on -- inactive and icon off`() = runTest {
- //given
+ // given
flashlightController.isEnabled = true
flashlightController.isAvailable = false
val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
- val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
- //when
+ // when
flashlightController.onFlashlightAvailabilityChanged(true)
val lastValue = values.last()
- //then
+ // then
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
- assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Active)
- assertEquals(R.drawable.ic_flashlight_on, (lastValue.icon as? Icon.Resource)?.res)
+ assertTrue(
+ (lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState
+ is ActivationState.Active
+ )
+ assertEquals(R.drawable.qs_flashlight_icon_on, (lastValue.icon as? Icon.Resource)?.res)
job.cancel()
}
@Test
fun `flashlight availability now on -- flashlight off -- inactive and icon off`() = runTest {
- //given
+ // given
flashlightController.isEnabled = false
flashlightController.isAvailable = false
val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
- val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
- //when
+ // when
flashlightController.onFlashlightAvailabilityChanged(true)
val lastValue = values.last()
- //then
+ // then
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
- assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Inactive)
- assertEquals(R.drawable.ic_flashlight_off, (lastValue.icon as? Icon.Resource)?.res)
+ assertTrue(
+ (lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState
+ is ActivationState.Inactive
+ )
+ assertEquals(R.drawable.qs_flashlight_icon_off, (lastValue.icon as? Icon.Resource)?.res)
job.cancel()
}
@Test
fun `flashlight available -- picker state default`() = runTest {
- //given
+ // given
flashlightController.isAvailable = true
- //when
+ // when
val result = underTest.getPickerScreenState()
- //then
+ // then
assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.Default)
}
@Test
fun `flashlight not available -- picker state unavailable`() = runTest {
- //given
+ // given
flashlightController.isAvailable = false
- //when
+ // when
val result = underTest.getPickerScreenState()
- //then
+ // then
assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 552b8cb..3b0169d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -57,7 +57,7 @@
private lateinit var testScope: TestScope
private lateinit var testDispatcher: TestDispatcher
- private lateinit var selectionManager: KeyguardQuickAffordanceSelectionManager
+ private lateinit var selectionManager: KeyguardQuickAffordanceLocalUserSelectionManager
private lateinit var settings: FakeSettings
@Before
@@ -75,7 +75,7 @@
testDispatcher = UnconfinedTestDispatcher()
testScope = TestScope(testDispatcher)
selectionManager =
- KeyguardQuickAffordanceSelectionManager(
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager =
mock {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index 6a2376b..67091a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -52,11 +52,11 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
-class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
+class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() {
@Mock private lateinit var userFileManager: UserFileManager
- private lateinit var underTest: KeyguardQuickAffordanceSelectionManager
+ private lateinit var underTest: KeyguardQuickAffordanceLocalUserSelectionManager
private lateinit var userTracker: FakeUserTracker
private lateinit var sharedPrefs: MutableMap<Int, SharedPreferences>
@@ -74,7 +74,7 @@
Dispatchers.setMain(dispatcher)
underTest =
- KeyguardQuickAffordanceSelectionManager(
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager = userFileManager,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
new file mode 100644
index 0000000..d7e9cf1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() {
+
+ @Mock private lateinit var userHandle: UserHandle
+
+ private lateinit var underTest: KeyguardQuickAffordanceRemoteUserSelectionManager
+
+ private lateinit var clientFactory: FakeKeyguardQuickAffordanceProviderClientFactory
+ private lateinit var testScope: TestScope
+ private lateinit var testDispatcher: TestDispatcher
+ private lateinit var userTracker: FakeUserTracker
+ private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient
+ private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(userHandle.identifier).thenReturn(UserHandle.USER_SYSTEM)
+ whenever(userHandle.isSystem).thenReturn(true)
+ client1 = FakeKeyguardQuickAffordanceProviderClient()
+ client2 = FakeKeyguardQuickAffordanceProviderClient()
+
+ userTracker = FakeUserTracker()
+ userTracker.set(
+ userInfos =
+ listOf(
+ UserInfo(
+ UserHandle.USER_SYSTEM,
+ "Primary",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ OTHER_USER_ID_1,
+ "Secondary 1",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ OTHER_USER_ID_2,
+ "Secondary 2",
+ /* flags= */ 0,
+ ),
+ ),
+ selectedUserIndex = 0,
+ )
+
+ clientFactory =
+ FakeKeyguardQuickAffordanceProviderClientFactory(
+ userTracker,
+ ) { selectedUserId ->
+ when (selectedUserId) {
+ OTHER_USER_ID_1 -> client1
+ OTHER_USER_ID_2 -> client2
+ else -> error("No client set-up for user $selectedUserId!")
+ }
+ }
+
+ testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+
+ underTest =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = testScope.backgroundScope,
+ userTracker = userTracker,
+ clientFactory = clientFactory,
+ userHandle = userHandle,
+ )
+ }
+
+ @Test
+ fun `selections - primary user process`() =
+ testScope.runTest {
+ val values = mutableListOf<Map<String, List<String>>>()
+ val job = launch { underTest.selections.toList(values) }
+
+ runCurrent()
+ assertThat(values.last()).isEmpty()
+
+ client1.insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+ )
+ client2.insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2,
+ )
+
+ userTracker.set(
+ userInfos = userTracker.userProfiles,
+ selectedUserIndex = 1,
+ )
+ runCurrent()
+ assertThat(values.last())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+ ),
+ )
+ )
+
+ userTracker.set(
+ userInfos = userTracker.userProfiles,
+ selectedUserIndex = 2,
+ )
+ runCurrent()
+ assertThat(values.last())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+ listOf(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2,
+ ),
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `selections - secondary user process - always empty`() =
+ testScope.runTest {
+ whenever(userHandle.isSystem).thenReturn(false)
+ val values = mutableListOf<Map<String, List<String>>>()
+ val job = launch { underTest.selections.toList(values) }
+
+ runCurrent()
+ assertThat(values.last()).isEmpty()
+
+ client1.insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+ )
+ userTracker.set(
+ userInfos = userTracker.userProfiles,
+ selectedUserIndex = 1,
+ )
+ runCurrent()
+ assertThat(values.last()).isEmpty()
+
+ job.cancel()
+ }
+
+ @Test
+ fun setSelections() =
+ testScope.runTest {
+ userTracker.set(
+ userInfos = userTracker.userProfiles,
+ selectedUserIndex = 1,
+ )
+ runCurrent()
+
+ underTest.setSelections(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceIds = listOf(FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1),
+ )
+ runCurrent()
+
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+ ),
+ )
+ )
+ }
+
+ companion object {
+ private const val OTHER_USER_ID_1 = UserHandle.MIN_SECONDARY_USER_ID + 1
+ private const val OTHER_USER_ID_2 = UserHandle.MIN_SECONDARY_USER_ID + 2
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 652fae9..c40488a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -17,17 +17,23 @@
package com.android.systemui.keyguard.data.repository
+import android.content.pm.UserInfo
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserFileManager
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -39,6 +45,7 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,14 +62,24 @@
private lateinit var config1: FakeKeyguardQuickAffordanceConfig
private lateinit var config2: FakeKeyguardQuickAffordanceConfig
+ private lateinit var userTracker: FakeUserTracker
+ private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient
+ private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient
@Before
fun setUp() {
- config1 = FakeKeyguardQuickAffordanceConfig("built_in:1")
- config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
+ config1 =
+ FakeKeyguardQuickAffordanceConfig(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1
+ )
+ config2 =
+ FakeKeyguardQuickAffordanceConfig(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2
+ )
val scope = CoroutineScope(IMMEDIATE)
- val selectionManager =
- KeyguardQuickAffordanceSelectionManager(
+ userTracker = FakeUserTracker()
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager =
mock<UserFileManager>().apply {
@@ -75,24 +92,45 @@
)
.thenReturn(FakeSharedPreferences())
},
- userTracker = FakeUserTracker(),
+ userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
+ client1 = FakeKeyguardQuickAffordanceProviderClient()
+ client2 = FakeKeyguardQuickAffordanceProviderClient()
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory =
+ FakeKeyguardQuickAffordanceProviderClientFactory(
+ userTracker,
+ ) { selectedUserId ->
+ when (selectedUserId) {
+ SECONDARY_USER_1 -> client1
+ SECONDARY_USER_2 -> client2
+ else -> error("No set-up client for user $selectedUserId!")
+ }
+ },
+ userHandle = UserHandle.SYSTEM,
+ )
underTest =
KeyguardQuickAffordanceRepository(
appContext = context,
scope = scope,
- selectionManager = selectionManager,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
scope = scope,
backgroundDispatcher = IMMEDIATE,
secureSettings = FakeSettings(),
- selectionsManager = selectionManager,
+ selectionsManager = localUserSelectionManager,
),
configs = setOf(config1, config2),
dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
}
@@ -187,7 +225,53 @@
)
}
- private suspend fun assertSelections(
+ @Test
+ fun `selections for secondary user`() =
+ runBlocking(IMMEDIATE) {
+ userTracker.set(
+ userInfos =
+ listOf(
+ UserInfo(
+ UserHandle.USER_SYSTEM,
+ "Primary",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ SECONDARY_USER_1,
+ "Secondary 1",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ SECONDARY_USER_2,
+ "Secondary 2",
+ /* flags= */ 0,
+ ),
+ ),
+ selectedUserIndex = 2,
+ )
+ client2.insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2,
+ )
+ val observed = mutableListOf<Map<String, List<KeyguardQuickAffordanceConfig>>>()
+ val job = underTest.selections.onEach { observed.add(it) }.launchIn(this)
+ yield()
+
+ assertSelections(
+ observed = observed.last(),
+ expected =
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(
+ config2,
+ ),
+ )
+ )
+
+ job.cancel()
+ }
+
+ private fun assertSelections(
observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
) {
@@ -201,5 +285,7 @@
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SECONDARY_USER_1 = UserHandle.MIN_SECONDARY_USER_ID + 1
+ private const val SECONDARY_USER_2 = UserHandle.MIN_SECONDARY_USER_ID + 2
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 13fc9fc..5deac19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -16,10 +16,13 @@
package com.android.systemui.keyguard.data.repository
+import android.graphics.Point
+import android.hardware.biometrics.BiometricSourceType
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.common.shared.model.Position
import com.android.systemui.doze.DozeHost
import com.android.systemui.doze.DozeMachine
@@ -27,9 +30,11 @@
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -57,9 +62,10 @@
@Mock private lateinit var dozeHost: DozeHost
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener
+ @Mock private lateinit var authController: AuthController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: KeyguardRepositoryImpl
@@ -76,6 +82,7 @@
keyguardStateController,
keyguardUpdateMonitor,
dozeTransitionListener,
+ authController,
)
}
@@ -198,7 +205,7 @@
fun dozeAmount() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<Float>()
- val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
+ val job = underTest.linearDozeAmount.onEach(values::add).launchIn(this)
val captor = argumentCaptor<StatusBarStateController.StateListener>()
verify(statusBarStateController).addCallback(captor.capture())
@@ -207,7 +214,7 @@
captor.value.onDozeAmountChanged(0.498f, 0.5f)
captor.value.onDozeAmountChanged(0.661f, 0.65f)
- assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
+ assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f))
job.cancel()
verify(statusBarStateController).removeCallback(captor.value)
@@ -217,25 +224,36 @@
fun wakefulness() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<WakefulnessModel>()
- val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
+ val job = underTest.wakefulness.onEach(values::add).launchIn(this)
val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
verify(wakefulnessLifecycle).addObserver(captor.capture())
+ whenever(wakefulnessLifecycle.wakefulness)
+ .thenReturn(WakefulnessLifecycle.WAKEFULNESS_WAKING)
captor.value.onStartedWakingUp()
+
+ whenever(wakefulnessLifecycle.wakefulness)
+ .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE)
captor.value.onFinishedWakingUp()
+
+ whenever(wakefulnessLifecycle.wakefulness)
+ .thenReturn(WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP)
captor.value.onStartedGoingToSleep()
+
+ whenever(wakefulnessLifecycle.wakefulness)
+ .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
captor.value.onFinishedGoingToSleep()
- assertThat(values)
+ assertThat(values.map { it.state })
.isEqualTo(
listOf(
// Initial value will be ASLEEP
- WakefulnessModel.ASLEEP,
- WakefulnessModel.STARTING_TO_WAKE,
- WakefulnessModel.AWAKE,
- WakefulnessModel.STARTING_TO_SLEEP,
- WakefulnessModel.ASLEEP,
+ WakefulnessState.ASLEEP,
+ WakefulnessState.STARTING_TO_WAKE,
+ WakefulnessState.AWAKE,
+ WakefulnessState.STARTING_TO_SLEEP,
+ WakefulnessState.ASLEEP,
)
)
@@ -329,14 +347,20 @@
val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
verify(biometricUnlockController).addBiometricModeListener(captor.capture())
- captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
- captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
- captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
- captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
- captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
- captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
- captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
- captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+ listOf(
+ BiometricUnlockController.MODE_NONE,
+ BiometricUnlockController.MODE_WAKE_AND_UNLOCK,
+ BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING,
+ BiometricUnlockController.MODE_SHOW_BOUNCER,
+ BiometricUnlockController.MODE_ONLY_WAKE,
+ BiometricUnlockController.MODE_UNLOCK_COLLAPSING,
+ BiometricUnlockController.MODE_DISMISS_BOUNCER,
+ BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM,
+ )
+ .forEach {
+ whenever(biometricUnlockController.mode).thenReturn(it)
+ captor.value.onModeChanged(it)
+ }
assertThat(values)
.isEqualTo(
@@ -420,4 +444,104 @@
job.cancel()
verify(dozeTransitionListener).removeCallback(listener)
}
+
+ @Test
+ fun fingerprintSensorLocation() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Point?>()
+ val job = underTest.fingerprintSensorLocation.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<AuthController.Callback>()
+ verify(authController).addCallback(captor.capture())
+
+ // An initial, null value should be initially emitted so that flows combined with this
+ // one
+ // emit values immediately. The sensor location is expected to be nullable, so anyone
+ // consuming it should handle that properly.
+ assertThat(values).isEqualTo(listOf(null))
+
+ listOf(Point(500, 500), Point(0, 0), null, Point(250, 250))
+ .onEach {
+ whenever(authController.fingerprintSensorLocation).thenReturn(it)
+ captor.value.onFingerprintLocationChanged()
+ }
+ .also { dispatchedSensorLocations ->
+ assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
+ }
+
+ job.cancel()
+ }
+
+ @Test
+ fun faceSensorLocation() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Point?>()
+ val job = underTest.faceSensorLocation.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<AuthController.Callback>()
+ verify(authController).addCallback(captor.capture())
+
+ // An initial, null value should be initially emitted so that flows combined with this
+ // one
+ // emit values immediately. The sensor location is expected to be nullable, so anyone
+ // consuming it should handle that properly.
+ assertThat(values).isEqualTo(listOf(null))
+
+ listOf(
+ Point(500, 500),
+ Point(0, 0),
+ null,
+ Point(250, 250),
+ )
+ .onEach {
+ whenever(authController.faceSensorLocation).thenReturn(it)
+ captor.value.onFaceSensorLocationChanged()
+ }
+ .also { dispatchedSensorLocations ->
+ assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
+ }
+
+ job.cancel()
+ }
+
+ @Test
+ fun biometricUnlockSource() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<BiometricUnlockSource?>()
+ val job = underTest.biometricUnlockSource.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(captor.capture())
+
+ // An initial, null value should be initially emitted so that flows combined with this
+ // one
+ // emit values immediately. The biometric unlock source is expected to be nullable, so
+ // anyone consuming it should handle that properly.
+ assertThat(values).isEqualTo(listOf(null))
+
+ listOf(
+ BiometricSourceType.FINGERPRINT,
+ BiometricSourceType.IRIS,
+ null,
+ BiometricSourceType.FACE,
+ BiometricSourceType.FINGERPRINT,
+ )
+ .onEach { biometricSourceType ->
+ captor.value.onBiometricAuthenticated(0, biometricSourceType, false)
+ }
+
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ null,
+ BiometricUnlockSource.FINGERPRINT_SENSOR,
+ BiometricUnlockSource.FACE_SENSOR,
+ null,
+ BiometricUnlockSource.FACE_SENSOR,
+ BiometricUnlockSource.FINGERPRINT_SENSOR,
+ )
+ )
+
+ job.cancel()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
new file mode 100644
index 0000000..d2db910
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.graphics.Point
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.statusbar.CircleReveal
+import com.android.systemui.statusbar.LightRevealEffect
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class LightRevealScrimRepositoryTest : SysuiTestCase() {
+ private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
+ private lateinit var underTest: LightRevealScrimRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ fakeKeyguardRepository = FakeKeyguardRepository()
+ underTest = LightRevealScrimRepositoryImpl(fakeKeyguardRepository, context)
+ }
+
+ @Test
+ fun `nextRevealEffect - effect switches between default and biometric with no dupes`() =
+ runTest {
+ val values = mutableListOf<LightRevealEffect>()
+ val job = launch { underTest.revealEffect.collect { values.add(it) } }
+
+ // We should initially emit the default reveal effect.
+ runCurrent()
+ values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT })
+
+ // The source and sensor locations are still null, so we should still be using the
+ // default reveal despite a biometric unlock.
+ fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+
+ runCurrent()
+ values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },)
+
+ // We got a source but still have no sensor locations, so should be sticking with
+ // the default effect.
+ fakeKeyguardRepository.setBiometricUnlockSource(
+ BiometricUnlockSource.FINGERPRINT_SENSOR
+ )
+
+ runCurrent()
+ values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },)
+
+ // We got a location for the face sensor, but we unlocked with fingerprint.
+ val faceLocation = Point(250, 0)
+ fakeKeyguardRepository.setFaceSensorLocation(faceLocation)
+
+ runCurrent()
+ values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },)
+
+ // Now we have fingerprint sensor locations, and wake and unlock via fingerprint.
+ val fingerprintLocation = Point(500, 500)
+ fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation)
+ fakeKeyguardRepository.setBiometricUnlockSource(
+ BiometricUnlockSource.FINGERPRINT_SENSOR
+ )
+ fakeKeyguardRepository.setBiometricUnlockState(
+ BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+ )
+
+ // We should now have switched to the circle reveal, at the fingerprint location.
+ runCurrent()
+ values.assertEffectsMatchPredicates(
+ { it == DEFAULT_REVEAL_EFFECT },
+ {
+ it is CircleReveal &&
+ it.centerX == fingerprintLocation.x &&
+ it.centerY == fingerprintLocation.y
+ },
+ )
+
+ // Subsequent wake and unlocks should not emit duplicate, identical CircleReveals.
+ val valuesPrevSize = values.size
+ fakeKeyguardRepository.setBiometricUnlockState(
+ BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+ )
+ fakeKeyguardRepository.setBiometricUnlockState(
+ BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+ )
+ assertEquals(valuesPrevSize, values.size)
+
+ // Non-biometric unlock, we should return to the default reveal.
+ fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+
+ runCurrent()
+ values.assertEffectsMatchPredicates(
+ { it == DEFAULT_REVEAL_EFFECT },
+ {
+ it is CircleReveal &&
+ it.centerX == fingerprintLocation.x &&
+ it.centerY == fingerprintLocation.y
+ },
+ { it == DEFAULT_REVEAL_EFFECT },
+ )
+
+ // We already have a face location, so switching to face source should update the
+ // CircleReveal.
+ fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+ runCurrent()
+ fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ runCurrent()
+
+ values.assertEffectsMatchPredicates(
+ { it == DEFAULT_REVEAL_EFFECT },
+ {
+ it is CircleReveal &&
+ it.centerX == fingerprintLocation.x &&
+ it.centerY == fingerprintLocation.y
+ },
+ { it == DEFAULT_REVEAL_EFFECT },
+ {
+ it is CircleReveal &&
+ it.centerX == faceLocation.x &&
+ it.centerY == faceLocation.y
+ },
+ )
+
+ job.cancel()
+ }
+
+ /**
+ * Asserts that the list of LightRevealEffects satisfies the list of predicates, in order, with
+ * no leftover elements.
+ */
+ private fun List<LightRevealEffect>.assertEffectsMatchPredicates(
+ vararg predicates: (LightRevealEffect) -> Boolean
+ ) {
+ println(this)
+ assertEquals(predicates.size, this.size)
+
+ assertFalse(
+ zip(predicates) { effect, predicate -> predicate(effect) }.any { matched -> !matched }
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index ba7c40b..1c1f039 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -29,9 +30,11 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
@@ -237,8 +240,8 @@
val qrCodeScanner =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
val scope = CoroutineScope(IMMEDIATE)
- val selectionManager =
- KeyguardQuickAffordanceSelectionManager(
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager =
mock<UserFileManager>().apply {
@@ -254,20 +257,30 @@
userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
scope = scope,
- selectionManager = selectionManager,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
scope = scope,
backgroundDispatcher = IMMEDIATE,
secureSettings = FakeSettings(),
- selectionsManager = selectionManager,
+ selectionsManager = localUserSelectionManager,
),
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
underTest =
KeyguardQuickAffordanceInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 8d0c4ef..11fe905 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -26,9 +27,11 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
@@ -98,8 +101,8 @@
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
val scope = CoroutineScope(IMMEDIATE)
- val selectionManager =
- KeyguardQuickAffordanceSelectionManager(
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager =
mock<UserFileManager>().apply {
@@ -115,20 +118,30 @@
userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
scope = scope,
- selectionManager = selectionManager,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
scope = scope,
backgroundDispatcher = IMMEDIATE,
secureSettings = FakeSettings(),
- selectionsManager = selectionManager,
+ selectionsManager = localUserSelectionManager,
),
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
featureFlags =
FakeFeatureFlags().apply {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
new file mode 100644
index 0000000..3166214
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.statusbar.LightRevealScrim
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class LightRevealScrimInteractorTest : SysuiTestCase() {
+ private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
+
+ private val keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(fakeKeyguardTransitionRepository)
+
+ private lateinit var underTest: LightRevealScrimInteractor
+
+ private val reveal1 =
+ object : LightRevealEffect {
+ override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {}
+ }
+
+ private val reveal2 =
+ object : LightRevealEffect {
+ override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {}
+ }
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ LightRevealScrimInteractor(
+ fakeKeyguardTransitionRepository,
+ keyguardTransitionInteractor,
+ fakeLightRevealScrimRepository
+ )
+ }
+
+ @Test
+ fun `lightRevealEffect - does not change during keyguard transition`() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<LightRevealEffect>()
+ val job = underTest.lightRevealEffect.onEach(values::add).launchIn(this)
+
+ fakeLightRevealScrimRepository.setRevealEffect(reveal1)
+
+ // The reveal effect shouldn't emit anything until a keyguard transition starts.
+ assertEquals(values.size, 0)
+
+ // Once it starts, it should emit reveal1.
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(transitionState = TransitionState.STARTED)
+ )
+ assertEquals(values, listOf(reveal1))
+
+ // Until the next transition starts, reveal2 should not be emitted.
+ fakeLightRevealScrimRepository.setRevealEffect(reveal2)
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(transitionState = TransitionState.RUNNING)
+ )
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(transitionState = TransitionState.FINISHED)
+ )
+ assertEquals(values, listOf(reveal1))
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(transitionState = TransitionState.STARTED)
+ )
+ assertEquals(values, listOf(reveal1, reveal2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun `revealAmount - inverted when appropriate`() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+ val job = underTest.revealAmount.onEach(values::add).launchIn(this)
+
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 0.3f
+ )
+ )
+
+ assertEquals(values, listOf(0.3f))
+
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0.3f
+ )
+ )
+
+ assertEquals(values, listOf(0.3f, 0.7f))
+
+ job.cancel()
+ }
+
+ @Test
+ fun `revealAmount - ignores transitions that do not affect reveal amount`() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+ val job = underTest.revealAmount.onEach(values::add).launchIn(this)
+
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.DOZING, to = KeyguardState.AOD, value = 0.3f)
+ )
+
+ assertEquals(values, emptyList<Float>())
+
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.AOD, to = KeyguardState.DOZING, value = 0.3f)
+ )
+
+ assertEquals(values, emptyList<Float>())
+
+ job.cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 32849cd..83a5d0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Intent
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -27,9 +28,11 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
@@ -121,8 +124,8 @@
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
val scope = CoroutineScope(IMMEDIATE)
- val selectionManager =
- KeyguardQuickAffordanceSelectionManager(
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager =
mock<UserFileManager>().apply {
@@ -138,17 +141,26 @@
userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
scope = scope,
- selectionManager = selectionManager,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
scope = scope,
backgroundDispatcher = IMMEDIATE,
secureSettings = FakeSettings(),
- selectionsManager = selectionManager,
+ selectionsManager = localUserSelectionManager,
),
configs =
setOf(
@@ -157,6 +169,7 @@
qrCodeScannerAffordanceConfig,
),
dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
underTest =
KeyguardBottomAreaViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
index 645b1cd..23466cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -41,6 +41,7 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestCoroutineScheduler
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,7 +56,7 @@
@Before
fun setUp() {
- utils = FooterActionsTestUtils(context, TestableLooper.get(this))
+ utils = FooterActionsTestUtils(context, TestableLooper.get(this), TestCoroutineScheduler())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 081a218..47afa70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.FakeFgsManagerController
import com.android.systemui.qs.QSSecurityFooterUtils
import com.android.systemui.qs.footer.FooterActionsTestUtils
@@ -44,12 +45,9 @@
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -62,16 +60,20 @@
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class FooterActionsViewModelTest : SysuiTestCase() {
+ private val testScope = TestScope()
private lateinit var utils: FooterActionsTestUtils
- private val testDispatcher = UnconfinedTestDispatcher(TestCoroutineScheduler())
@Before
fun setUp() {
- utils = FooterActionsTestUtils(context, TestableLooper.get(this))
+ utils = FooterActionsTestUtils(context, TestableLooper.get(this), testScope.testScheduler)
+ }
+
+ private fun runTest(block: suspend TestScope.() -> Unit) {
+ testScope.runTest(testBody = block)
}
@Test
- fun settingsButton() = runBlockingTest {
+ fun settingsButton() = runTest {
val underTest = utils.footerActionsViewModel(showPowerButton = false)
val settings = underTest.settings
@@ -87,7 +89,7 @@
}
@Test
- fun powerButton() = runBlockingTest {
+ fun powerButton() = runTest {
// Without power button.
val underTestWithoutPower = utils.footerActionsViewModel(showPowerButton = false)
assertThat(underTestWithoutPower.power).isNull()
@@ -114,7 +116,7 @@
}
@Test
- fun userSwitcher() = runBlockingTest {
+ fun userSwitcher() = runTest {
val picture: Drawable = mock()
val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
val settings = FakeSettings()
@@ -135,7 +137,6 @@
showPowerButton = false,
footerActionsInteractor =
utils.footerActionsInteractor(
- bgDispatcher = testDispatcher,
userSwitcherRepository =
utils.userSwitcherRepository(
userTracker = userTracker,
@@ -143,22 +144,12 @@
userManager = userManager,
userInfoController = userInfoController,
userSwitcherController = userSwitcherControllerWrapper.controller,
- bgDispatcher = testDispatcher,
),
)
)
// Collect the user switcher into currentUserSwitcher.
- var currentUserSwitcher: FooterActionsButtonViewModel? = null
- val job = launch { underTest.userSwitcher.collect { currentUserSwitcher = it } }
- fun currentUserSwitcher(): FooterActionsButtonViewModel? {
- // Make sure we finish collecting the current user switcher. This is necessary because
- // combined flows launch multiple coroutines in the current scope so we need to make
- // sure we process all coroutines triggered by our flow collection before we make
- // assertions on the current buttons.
- advanceUntilIdle()
- return currentUserSwitcher
- }
+ val currentUserSwitcher = collectLastValue(underTest.userSwitcher)
// The user switcher is disabled.
assertThat(currentUserSwitcher()).isNull()
@@ -203,12 +194,10 @@
// in guest mode.
userInfoController.updateInfo { this.picture = mock<UserIconDrawable>() }
assertThat(iconTint()).isNull()
-
- job.cancel()
}
@Test
- fun security() = runBlockingTest {
+ fun security() = runTest {
val securityController = FakeSecurityController()
val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
@@ -224,22 +213,15 @@
footerActionsInteractor =
utils.footerActionsInteractor(
qsSecurityFooterUtils = qsSecurityFooterUtils,
- bgDispatcher = testDispatcher,
securityRepository =
utils.securityRepository(
securityController = securityController,
- bgDispatcher = testDispatcher,
),
),
)
// Collect the security model into currentSecurity.
- var currentSecurity: FooterActionsSecurityButtonViewModel? = null
- val job = launch { underTest.security.collect { currentSecurity = it } }
- fun currentSecurity(): FooterActionsSecurityButtonViewModel? {
- advanceUntilIdle()
- return currentSecurity
- }
+ val currentSecurity = collectLastValue(underTest.security)
// By default, we always return a null SecurityButtonConfig.
assertThat(currentSecurity()).isNull()
@@ -270,12 +252,10 @@
security = currentSecurity()
assertThat(security).isNotNull()
assertThat(security!!.onClick).isNull()
-
- job.cancel()
}
@Test
- fun foregroundServices() = runBlockingTest {
+ fun foregroundServices() = runTest {
val securityController = FakeSecurityController()
val fgsManagerController =
FakeFgsManagerController(
@@ -300,21 +280,14 @@
securityRepository =
utils.securityRepository(
securityController,
- bgDispatcher = testDispatcher,
),
foregroundServicesRepository =
utils.foregroundServicesRepository(fgsManagerController),
- bgDispatcher = testDispatcher,
),
)
// Collect the security model into currentSecurity.
- var currentForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
- val job = launch { underTest.foregroundServices.collect { currentForegroundServices = it } }
- fun currentForegroundServices(): FooterActionsForegroundServicesButtonViewModel? {
- advanceUntilIdle()
- return currentForegroundServices
- }
+ val currentForegroundServices = collectLastValue(underTest.foregroundServices)
// We don't show the foreground services button if the number of running packages is not
// > 1.
@@ -356,12 +329,10 @@
}
securityController.updateState {}
assertThat(currentForegroundServices()?.displayText).isFalse()
-
- job.cancel()
}
@Test
- fun observeDeviceMonitoringDialogRequests() = runBlockingTest {
+ fun observeDeviceMonitoringDialogRequests() = runTest {
val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
val broadcastDispatcher = mock<BroadcastDispatcher>()
@@ -390,7 +361,6 @@
utils.footerActionsInteractor(
qsSecurityFooterUtils = qsSecurityFooterUtils,
broadcastDispatcher = broadcastDispatcher,
- bgDispatcher = testDispatcher,
),
)
@@ -415,7 +385,4 @@
underTest.onVisibilityChangeRequested(visible = true)
assertThat(underTest.isVisible.value).isTrue()
}
-
- private fun runBlockingTest(block: suspend TestScope.() -> Unit) =
- runTest(testDispatcher) { block() }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 69a4559..b6f74f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -131,6 +131,7 @@
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -383,7 +384,8 @@
mInteractionJankMonitor, mShadeExpansionStateManager),
mKeyguardBypassController,
mDozeParameters,
- mScreenOffAnimationController);
+ mScreenOffAnimationController,
+ mock(NotificationWakeUpCoordinatorLogger.class));
mConfigurationController = new ConfigurationControllerImpl(mContext);
PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
mContext,
@@ -499,8 +501,18 @@
mDumpManager);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
+ null,
() -> {},
mNotificationShelfController);
+ mNotificationPanelViewController.setTrackingStartedListener(() -> {});
+ mNotificationPanelViewController.setOpenCloseListener(
+ new NotificationPanelViewController.OpenCloseListener() {
+ @Override
+ public void onClosingFinished() {}
+
+ @Override
+ public void onOpenStarted() {}
+ });
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
@@ -831,7 +843,7 @@
public void handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
when(mCommandQueue.panelsEnabled()).thenReturn(true);
when(mView.isEnabled()).thenReturn(true);
- MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0);
+ MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0);
mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
@@ -839,6 +851,17 @@
}
@Test
+ public void handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
+ when(mCommandQueue.panelsEnabled()).thenReturn(true);
+ when(mView.isEnabled()).thenReturn(true);
+ MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0);
+
+ mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
+
+ verify(mView, never()).dispatchTouchEvent(event);
+ }
+
+ @Test
public void testA11y_initializeNode() {
AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index ea311da..21aae00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -17,6 +17,7 @@
import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.GROUP_ALERT_SUMMARY;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -33,6 +34,8 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -390,6 +393,127 @@
assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
}
+ private long makeWhenHoursAgo(long hoursAgo) {
+ return System.currentTimeMillis() - (1000 * 60 * 60 * hoursAgo);
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_flagDisabled() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(false);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.getSbn().getNotification().when = makeWhenHoursAgo(25);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_whenNow() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_whenRecent() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.getSbn().getNotification().when = makeWhenHoursAgo(13);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_whenZero() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.getSbn().getNotification().when = 0L;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(0L), anyLong(),
+ eq("when <= 0"));
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_whenNegative() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.getSbn().getNotification().when = -1L;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(-1L), anyLong(),
+ eq("when <= 0"));
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+ long when = makeWhenHoursAgo(25);
+
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silent= */ false);
+ entry.getSbn().getNotification().when = when;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(when), anyLong(),
+ eq("full-screen intent"));
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_isForegroundService() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+ long when = makeWhenHoursAgo(25);
+
+ NotificationEntry entry = createFgsNotification(IMPORTANCE_HIGH);
+ entry.getSbn().getNotification().when = when;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(when), anyLong(),
+ eq("foreground service"));
+ }
+
+ @Test
+ public void testShouldNotHeadsUp_oldWhen() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+ long when = makeWhenHoursAgo(25);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.getSbn().getNotification().when = when;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+
+ verify(mLogger).logNoHeadsUpOldWhen(eq(entry), eq(when), anyLong());
+ verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
+ }
+
@Test
public void testShouldNotFullScreen_notPendingIntent_withStrictFlag() throws Exception {
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
@@ -763,6 +887,16 @@
return createNotification(importance, n);
}
+ private NotificationEntry createFgsNotification(int importance) {
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setContentTitle("title")
+ .setContentText("content text")
+ .setFlag(FLAG_FOREGROUND_SERVICE, true)
+ .build();
+
+ return createNotification(importance, n);
+ }
+
private final NotificationInterruptSuppressor
mSuppressAwakeHeadsUp =
new NotificationInterruptSuppressor() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
new file mode 100644
index 0000000..2d23f3c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.PluginManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.NotificationMediaManager
+import com.android.systemui.statusbar.SmartReplyController
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.SmartReplyConstants
+import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
+import junit.framework.Assert
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ExpandableNotificationRowControllerTest : SysuiTestCase() {
+
+ private val appName = "MyApp"
+ private val notifKey = "MyNotifKey"
+
+ private val view: ExpandableNotificationRow = mock()
+ private val activableNotificationViewController: ActivatableNotificationViewController = mock()
+ private val rivSubComponentFactory: RemoteInputViewSubcomponent.Factory = mock()
+ private val metricsLogger: MetricsLogger = mock()
+ private val logBufferLogger: NotificationRowLogger = mock()
+ private val listContainer: NotificationListContainer = mock()
+ private val mediaManager: NotificationMediaManager = mock()
+ private val smartReplyConstants: SmartReplyConstants = mock()
+ private val smartReplyController: SmartReplyController = mock()
+ private val pluginManager: PluginManager = mock()
+ private val systemClock: SystemClock = mock()
+ private val keyguardBypassController: KeyguardBypassController = mock()
+ private val groupMembershipManager: GroupMembershipManager = mock()
+ private val groupExpansionManager: GroupExpansionManager = mock()
+ private val rowContentBindStage: RowContentBindStage = mock()
+ private val notifLogger: NotificationLogger = mock()
+ private val headsUpManager: HeadsUpManager = mock()
+ private val onExpandClickListener: ExpandableNotificationRow.OnExpandClickListener = mock()
+ private val statusBarStateController: StatusBarStateController = mock()
+ private val gutsManager: NotificationGutsManager = mock()
+ private val onUserInteractionCallback: OnUserInteractionCallback = mock()
+ private val falsingManager: FalsingManager = mock()
+ private val falsingCollector: FalsingCollector = mock()
+ private val featureFlags: FeatureFlags = mock()
+ private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock()
+ private val bubblesManager: BubblesManager = mock()
+ private val dragController: ExpandableNotificationRowDragController = mock()
+ private lateinit var controller: ExpandableNotificationRowController
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ controller =
+ ExpandableNotificationRowController(
+ view,
+ activableNotificationViewController,
+ rivSubComponentFactory,
+ metricsLogger,
+ logBufferLogger,
+ listContainer,
+ mediaManager,
+ smartReplyConstants,
+ smartReplyController,
+ pluginManager,
+ systemClock,
+ appName,
+ notifKey,
+ keyguardBypassController,
+ groupMembershipManager,
+ groupExpansionManager,
+ rowContentBindStage,
+ notifLogger,
+ headsUpManager,
+ onExpandClickListener,
+ statusBarStateController,
+ gutsManager,
+ /*allowLongPress=*/ false,
+ onUserInteractionCallback,
+ falsingManager,
+ falsingCollector,
+ featureFlags,
+ peopleNotificationIdentifier,
+ Optional.of(bubblesManager),
+ dragController
+ )
+ }
+
+ @After
+ fun tearDown() {
+ disallowTestableLooperAsMainThread()
+ }
+
+ @Test
+ fun offerKeepInParent_parentDismissed() {
+ whenever(featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION))
+ .thenReturn(true)
+ whenever(view.isParentDismissed).thenReturn(true)
+
+ Assert.assertTrue(controller.offerToKeepInParentForAnimation())
+ Mockito.verify(view).setKeepInParentForDismissAnimation(true)
+ }
+
+ @Test
+ fun offerKeepInParent_parentNotDismissed() {
+ whenever(featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION))
+ .thenReturn(true)
+
+ Assert.assertFalse(controller.offerToKeepInParentForAnimation())
+ Mockito.verify(view, never()).setKeepInParentForDismissAnimation(anyBoolean())
+ }
+
+ @Test
+ fun removeFromParent_keptForAnimation() {
+ val parentView: ExpandableNotificationRow = mock()
+ whenever(view.notificationParent).thenReturn(parentView)
+ whenever(view.keepInParentForDismissAnimation()).thenReturn(true)
+
+ Assert.assertTrue(controller.removeFromParentIfKeptForAnimation())
+ Mockito.verify(parentView).removeChildNotification(view)
+ }
+
+ @Test
+ fun removeFromParent_notKeptForAnimation() {
+ val parentView: ExpandableNotificationRow = mock()
+ whenever(view.notificationParent).thenReturn(parentView)
+
+ Assert.assertFalse(controller.removeFromParentIfKeptForAnimation())
+ Mockito.verifyNoMoreInteractions(parentView)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 5fd9448..fb31bef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -301,7 +301,8 @@
public ExpandableNotificationRow createBubble()
throws Exception {
Notification n = createNotification(false /* isGroupSummary */,
- null /* groupKey */, makeBubbleMetadata(null));
+ null /* groupKey */,
+ makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */));
n.flags |= FLAG_BUBBLE;
ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE,
mDefaultInflationFlags, IMPORTANCE_HIGH);
@@ -334,7 +335,8 @@
public ExpandableNotificationRow createBubbleInGroup()
throws Exception {
Notification n = createNotification(false /* isGroupSummary */,
- GROUP_KEY /* groupKey */, makeBubbleMetadata(null));
+ GROUP_KEY /* groupKey */,
+ makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */));
n.flags |= FLAG_BUBBLE;
ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE,
mDefaultInflationFlags, IMPORTANCE_HIGH);
@@ -350,7 +352,7 @@
* @param deleteIntent the intent to assign to {@link BubbleMetadata#deleteIntent}
*/
public NotificationEntry createBubble(@Nullable PendingIntent deleteIntent) {
- return createBubble(makeBubbleMetadata(deleteIntent), USER_HANDLE);
+ return createBubble(makeBubbleMetadata(deleteIntent, false /* autoExpand */), USER_HANDLE);
}
/**
@@ -359,7 +361,16 @@
* @param handle the user to associate with this bubble.
*/
public NotificationEntry createBubble(UserHandle handle) {
- return createBubble(makeBubbleMetadata(null), handle);
+ return createBubble(makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */),
+ handle);
+ }
+
+ /**
+ * Returns an {@link NotificationEntry} that should be shown as a auto-expanded bubble.
+ */
+ public NotificationEntry createAutoExpandedBubble() {
+ return createBubble(makeBubbleMetadata(null /* deleteIntent */, true /* autoExpand */),
+ USER_HANDLE);
}
/**
@@ -567,7 +578,7 @@
assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
}
- private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent) {
+ private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent, boolean autoExpand) {
Intent target = new Intent(mContext, BubblesTestActivity.class);
PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target,
PendingIntent.FLAG_MUTABLE);
@@ -576,6 +587,7 @@
Icon.createWithResource(mContext, R.drawable.android))
.setDeleteIntent(deleteIntent)
.setDesiredHeight(314)
+ .setAutoExpandBubble(autoExpand)
.build();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index ed84e42..521e518 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -106,6 +106,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.PluginDependencyProvider;
@@ -212,6 +213,7 @@
@Mock private NotificationPanelView mNotificationPanelView;
@Mock private IStatusBarService mBarService;
@Mock private IDreamManager mDreamManager;
+ @Mock private LightRevealScrimViewModel mLightRevealScrimViewModel;
@Mock private ScrimController mScrimController;
@Mock private DozeScrimController mDozeScrimController;
@Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@@ -497,7 +499,8 @@
mDeviceStateManager,
mWiredChargingRippleController,
mDreamManager,
- mCameraLauncherLazy) {
+ mCameraLauncherLazy,
+ () -> mLightRevealScrimViewModel) {
@Override
protected ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index d3b5418..df7ee43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -39,6 +39,7 @@
import android.content.res.ColorStateList;
import android.graphics.Color;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -398,6 +399,8 @@
@Test
public void testShow_delaysIfFaceAuthIsRunning() {
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+ .thenReturn(true);
when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
mBouncer.show(true /* reset */);
@@ -410,9 +413,10 @@
}
@Test
- public void testShow_doesNotDelaysIfFaceAuthIsLockedOut() {
+ public void testShow_doesNotDelaysIfFaceAuthIsNotAllowed() {
when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
- when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+ .thenReturn(false);
mBouncer.show(true /* reset */);
verify(mHandler, never()).postDelayed(any(), anyLong());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index de71e2c..e475905 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -1442,16 +1442,30 @@
@Test
public void testNotificationTransparency_followsTransitionToFullShade() {
+ mScrimController.setClipsQsScrim(true);
+
mScrimController.transitionTo(SHADE_LOCKED);
mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
+
+ assertScrimTinted(Map.of(
+ mScrimInFront, false,
+ mScrimBehind, true,
+ mNotificationsScrim, false
+ ));
+
float shadeLockedAlpha = mNotificationsScrim.getViewAlpha();
mScrimController.transitionTo(ScrimState.KEYGUARD);
mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
float keyguardAlpha = mNotificationsScrim.getViewAlpha();
- mScrimController.setClipsQsScrim(true);
+ assertScrimTinted(Map.of(
+ mScrimInFront, true,
+ mScrimBehind, true,
+ mNotificationsScrim, true
+ ));
+
float progress = 0.5f;
float lsNotifProgress = 0.3f;
mScrimController.setTransitionToFullShadeProgress(progress, lsNotifProgress);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
index 76016a1..5a6bb30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
@@ -38,9 +38,9 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-class AirplaneModeViewModelTest : SysuiTestCase() {
+class AirplaneModeViewModelImplTest : SysuiTestCase() {
- private lateinit var underTest: AirplaneModeViewModel
+ private lateinit var underTest: AirplaneModeViewModelImpl
@Mock private lateinit var logger: ConnectivityPipelineLogger
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -57,7 +57,7 @@
scope = CoroutineScope(IMMEDIATE)
underTest =
- AirplaneModeViewModel(
+ AirplaneModeViewModelImpl(
interactor,
logger,
scope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index 71b8bab..b38497a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -35,8 +35,9 @@
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@SmallTest
-class WifiInteractorTest : SysuiTestCase() {
+class WifiInteractorImplTest : SysuiTestCase() {
private lateinit var underTest: WifiInteractor
@@ -47,7 +48,7 @@
fun setUp() {
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
- underTest = WifiInteractor(connectivityRepository, wifiRepository)
+ underTest = WifiInteractorImpl(connectivityRepository, wifiRepository)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 37457b3..5c16e129 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -32,12 +32,14 @@
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
@@ -86,9 +88,9 @@
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
- interactor = WifiInteractor(connectivityRepository, wifiRepository)
+ interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
scope = CoroutineScope(Dispatchers.Unconfined)
- airplaneModeViewModel = AirplaneModeViewModel(
+ airplaneModeViewModel = AirplaneModeViewModelImpl(
AirplaneModeInteractor(
airplaneModeRepository,
connectivityRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index a1afcd7..3001b81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
@@ -37,6 +38,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
import com.google.common.truth.Truth.assertThat
@@ -81,10 +83,10 @@
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
- interactor = WifiInteractor(connectivityRepository, wifiRepository)
+ interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
airplaneModeViewModel =
- AirplaneModeViewModel(
+ AirplaneModeViewModelImpl(
AirplaneModeInteractor(
airplaneModeRepository,
connectivityRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 7d2c560..6a6b2a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
@@ -30,6 +31,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import com.google.common.truth.Truth.assertThat
@@ -73,9 +75,9 @@
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
- interactor = WifiInteractor(connectivityRepository, wifiRepository)
+ interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
- airplaneModeViewModel = AirplaneModeViewModel(
+ airplaneModeViewModel = AirplaneModeViewModelImpl(
AirplaneModeInteractor(
airplaneModeRepository,
connectivityRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 50d239d..78b0cbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -761,7 +761,7 @@
}
@Test
- fun `users - secondary user - no guest user`() =
+ fun `users - secondary user - guest user can be switched to`() =
runBlocking(IMMEDIATE) {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
@@ -770,8 +770,8 @@
var res: List<UserModel>? = null
val job = underTest.users.onEach { res = it }.launchIn(this)
- assertThat(res?.size == 2).isTrue()
- assertThat(res?.find { it.isGuest }).isNull()
+ assertThat(res?.size == 3).isTrue()
+ assertThat(res?.find { it.isGuest }).isNotNull()
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 4b6bdac..784a26b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -47,18 +47,14 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestDispatcher
-import kotlinx.coroutines.test.TestResult
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -89,7 +85,6 @@
private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
- private lateinit var injectedScope: CoroutineScope
@Before
fun setUp() {
@@ -104,7 +99,6 @@
testDispatcher = UnconfinedTestDispatcher()
testScope = TestScope(testDispatcher)
- injectedScope = CoroutineScope(testScope.coroutineContext + SupervisorJob())
userRepository = FakeUserRepository()
runBlocking {
userRepository.setSettings(
@@ -118,14 +112,14 @@
powerRepository = FakePowerRepository()
val refreshUsersScheduler =
RefreshUsersScheduler(
- applicationScope = injectedScope,
+ applicationScope = testScope.backgroundScope,
mainDispatcher = testDispatcher,
repository = userRepository,
)
val guestUserInteractor =
GuestUserInteractor(
applicationContext = context,
- applicationScope = injectedScope,
+ applicationScope = testScope.backgroundScope,
mainDispatcher = testDispatcher,
backgroundDispatcher = testDispatcher,
manager = manager,
@@ -154,7 +148,7 @@
set(Flags.FULL_SCREEN_USER_SWITCHER, false)
},
manager = manager,
- applicationScope = injectedScope,
+ applicationScope = testScope.backgroundScope,
telephonyInteractor =
TelephonyInteractor(
repository = FakeTelephonyRepository(),
@@ -175,7 +169,7 @@
}
@Test
- fun users() = selfCancelingTest {
+ fun users() = testScope.runTest {
val userInfos =
listOf(
UserInfo(
@@ -210,26 +204,26 @@
assertUserViewModel(
viewModel = userViewModels.last()[0],
viewKey = 0,
- name = "zero",
+ name = Text.Loaded("zero"),
isSelectionMarkerVisible = true,
)
assertUserViewModel(
viewModel = userViewModels.last()[1],
viewKey = 1,
- name = "one",
+ name = Text.Loaded("one"),
isSelectionMarkerVisible = false,
)
assertUserViewModel(
viewModel = userViewModels.last()[2],
viewKey = 2,
- name = "two",
+ name = Text.Loaded("two"),
isSelectionMarkerVisible = false,
)
job.cancel()
}
@Test
- fun `maximumUserColumns - few users`() = selfCancelingTest {
+ fun `maximumUserColumns - few users`() = testScope.runTest {
setUsers(count = 2)
val values = mutableListOf<Int>()
val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
@@ -240,7 +234,7 @@
}
@Test
- fun `maximumUserColumns - many users`() = selfCancelingTest {
+ fun `maximumUserColumns - many users`() = testScope.runTest {
setUsers(count = 5)
val values = mutableListOf<Int>()
val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
@@ -250,7 +244,7 @@
}
@Test
- fun `isOpenMenuButtonVisible - has actions - true`() = selfCancelingTest {
+ fun `isOpenMenuButtonVisible - has actions - true`() = testScope.runTest {
setUsers(2)
val isVisible = mutableListOf<Boolean>()
@@ -261,7 +255,7 @@
}
@Test
- fun `isOpenMenuButtonVisible - no actions - false`() = selfCancelingTest {
+ fun `isOpenMenuButtonVisible - no actions - false`() = testScope.runTest {
val userInfos = setUsers(2)
userRepository.setSelectedUserInfo(userInfos[1])
keyguardRepository.setKeyguardShowing(true)
@@ -275,7 +269,7 @@
}
@Test
- fun menu() = selfCancelingTest {
+ fun menu() = testScope.runTest {
val isMenuVisible = mutableListOf<Boolean>()
val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) }
assertThat(isMenuVisible.last()).isFalse()
@@ -290,7 +284,7 @@
}
@Test
- fun `menu actions`() = selfCancelingTest {
+ fun `menu actions`() = testScope.runTest {
setUsers(2)
val actions = mutableListOf<List<UserActionViewModel>>()
val job = launch(testDispatcher) { underTest.menu.toList(actions) }
@@ -309,7 +303,7 @@
}
@Test
- fun `isFinishRequested - finishes when user is switched`() = selfCancelingTest {
+ fun `isFinishRequested - finishes when user is switched`() = testScope.runTest {
val userInfos = setUsers(count = 2)
val isFinishRequested = mutableListOf<Boolean>()
val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
@@ -323,7 +317,7 @@
}
@Test
- fun `isFinishRequested - finishes when the screen turns off`() = selfCancelingTest {
+ fun `isFinishRequested - finishes when the screen turns off`() = testScope.runTest {
setUsers(count = 2)
powerRepository.setInteractive(true)
val isFinishRequested = mutableListOf<Boolean>()
@@ -338,7 +332,7 @@
}
@Test
- fun `isFinishRequested - finishes when cancel button is clicked`() = selfCancelingTest {
+ fun `isFinishRequested - finishes when cancel button is clicked`() = testScope.runTest {
setUsers(count = 2)
powerRepository.setInteractive(true)
val isFinishRequested = mutableListOf<Boolean>()
@@ -356,6 +350,93 @@
job.cancel()
}
+ @Test
+ fun `guest selected -- name is exit guest`() = testScope.runTest {
+ val userInfos =
+ listOf(
+ UserInfo(
+ /* id= */ 0,
+ /* name= */ "zero",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ UserInfo(
+ /* id= */ 1,
+ /* name= */ "one",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_GUEST,
+ ),
+ )
+
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+
+ val userViewModels = mutableListOf<List<UserViewModel>>()
+ val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
+
+ assertThat(userViewModels.last()).hasSize(2)
+ assertUserViewModel(
+ viewModel = userViewModels.last()[0],
+ viewKey = 0,
+ name = Text.Loaded("zero"),
+ isSelectionMarkerVisible = false,
+ )
+ assertUserViewModel(
+ viewModel = userViewModels.last()[1],
+ viewKey = 1,
+ name = Text.Resource(
+ com.android.settingslib.R.string.guest_exit_quick_settings_button
+ ),
+ isSelectionMarkerVisible = true,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `guest not selected -- name is guest`() = testScope.runTest {
+ val userInfos =
+ listOf(
+ UserInfo(
+ /* id= */ 0,
+ /* name= */ "zero",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ UserInfo(
+ /* id= */ 1,
+ /* name= */ "one",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_GUEST,
+ ),
+ )
+
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ runCurrent()
+
+ val userViewModels = mutableListOf<List<UserViewModel>>()
+ val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
+
+ assertThat(userViewModels.last()).hasSize(2)
+ assertUserViewModel(
+ viewModel = userViewModels.last()[0],
+ viewKey = 0,
+ name = Text.Loaded("zero"),
+ isSelectionMarkerVisible = true,
+ )
+ assertUserViewModel(
+ viewModel = userViewModels.last()[1],
+ viewKey = 1,
+ name = Text.Loaded("one"),
+ isSelectionMarkerVisible = false,
+ )
+ job.cancel()
+ }
+
private suspend fun setUsers(count: Int): List<UserInfo> {
val userInfos =
(0 until count).map { index ->
@@ -384,26 +465,18 @@
private fun assertUserViewModel(
viewModel: UserViewModel?,
viewKey: Int,
- name: String,
+ name: Text,
isSelectionMarkerVisible: Boolean,
) {
checkNotNull(viewModel)
assertThat(viewModel.viewKey).isEqualTo(viewKey)
- assertThat(viewModel.name).isEqualTo(Text.Loaded(name))
+ assertThat(viewModel.name).isEqualTo(name)
assertThat(viewModel.isSelectionMarkerVisible).isEqualTo(isSelectionMarkerVisible)
assertThat(viewModel.alpha)
.isEqualTo(LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA)
assertThat(viewModel.onClicked).isNotNull()
}
- private fun selfCancelingTest(
- block: suspend TestScope.() -> Unit,
- ): TestResult =
- testScope.runTest {
- block()
- injectedScope.coroutineContext[Job.Key]?.cancelAndJoin()
- }
-
companion object {
private const val SUPERVISED_USER_CREATION_PACKAGE = "com.some.package"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index d42f757..b9dfc27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -1423,13 +1423,43 @@
assertStackCollapsed();
// Post status bar state change update with the same value
mBubbleController.onStatusBarStateChanged(false);
- // Stack should remain collapsedb
+ // Stack should remain collapsed
assertStackCollapsed();
// Post status bar state change which should trigger bubble to expand
mBubbleController.onStatusBarStateChanged(true);
assertStackExpanded();
}
+ /**
+ * Test to verify behavior for the following scenario:
+ * <ol>
+ * <li>device is locked with keyguard on, status bar shade state updates to
+ * <code>false</code></li>
+ * <li>notification entry is marked to be a bubble and it is set to auto-expand</li>
+ * <li>device unlock starts, status bar shade state receives another update to
+ * <code>false</code></li>
+ * <li>device is unlocked and status bar shade state is set to <code>true</code></li>
+ * <li>bubble should be expanded</li>
+ * </ol>
+ */
+ @Test
+ public void testOnStatusBarStateChanged_newAutoExpandedBubbleRemainsExpanded() {
+ // Set device as locked
+ mBubbleController.onStatusBarStateChanged(false);
+
+ // Create a auto-expanded bubble
+ NotificationEntry entry = mNotificationTestHelper.createAutoExpandedBubble();
+ mEntryListener.onEntryAdded(entry);
+
+ // When unlocking, we may receive duplicate updates with shade=false, ensure they don't
+ // clear the expanded state
+ mBubbleController.onStatusBarStateChanged(false);
+ mBubbleController.onStatusBarStateChanged(true);
+
+ // After unlocking, stack should be expanded
+ assertStackExpanded();
+ }
+
@Test
public void testSetShouldAutoExpand_notifiesFlagChanged() {
mBubbleController.updateBubble(mBubbleEntry);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index fa3cc99..bf2235a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -136,6 +136,8 @@
InstrumentationRegistry.getArguments());
if (TestableLooper.get(this) != null) {
TestableLooper.get(this).processAllMessages();
+ // Must remove static reference to this test object to prevent leak (b/261039202)
+ TestableLooper.remove(this);
}
disallowTestableLooperAsMainThread();
mContext.cleanUpReceivers(this.getClass().getSimpleName());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index 52e0c982..ad086ff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -22,12 +22,12 @@
import android.os.Handler
import android.os.Looper
import android.os.UserHandle
-import android.util.ArraySet
import android.util.Log
import com.android.systemui.SysuiTestableContext
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserTracker
+import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor
class FakeBroadcastDispatcher(
@@ -50,7 +50,7 @@
PendingRemovalStore(logger)
) {
- val registeredReceivers = ArraySet<BroadcastReceiver>()
+ val registeredReceivers: MutableSet<BroadcastReceiver> = ConcurrentHashMap.newKeySet()
override fun registerReceiverWithHandler(
receiver: BroadcastReceiver,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
new file mode 100644
index 0000000..b7a8d2e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.coroutines
+
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+
+/** Collect [flow] in a new [Job] and return a getter for the last collected value. */
+fun <T> TestScope.collectLastValue(
+ flow: Flow<T>,
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+): () -> T? {
+ var lastValue: T? = null
+ backgroundScope.launch(context, start) { flow.collect { lastValue = it } }
+ return {
+ runCurrent()
+ lastValue
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt
new file mode 100644
index 0000000..d85dd2e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient
+
+class FakeKeyguardQuickAffordanceProviderClientFactory(
+ private val userTracker: UserTracker,
+ private val callback: (Int) -> KeyguardQuickAffordanceProviderClient = {
+ FakeKeyguardQuickAffordanceProviderClient()
+ },
+) : KeyguardQuickAffordanceProviderClientFactory {
+
+ override fun create(): KeyguardQuickAffordanceProviderClient {
+ return callback(userTracker.userId)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 3601667..5c2a915 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -17,11 +17,15 @@
package com.android.systemui.keyguard.data.repository
+import android.graphics.Point
import com.android.systemui.common.shared.model.Position
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -49,7 +53,7 @@
override val isDreaming: Flow<Boolean> = _isDreaming
private val _dozeAmount = MutableStateFlow(0f)
- override val dozeAmount: Flow<Float> = _dozeAmount
+ override val linearDozeAmount: Flow<Float> = _dozeAmount
private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
override val statusBarState: Flow<StatusBarState> = _statusBarState
@@ -57,8 +61,16 @@
private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel())
override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel
- private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
- override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
+ private val _wakefulnessModel =
+ MutableStateFlow(
+ WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ false,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+ override val wakefulness: Flow<WakefulnessModel> = _wakefulnessModel
private val _isUdfpsSupported = MutableStateFlow(false)
@@ -71,6 +83,15 @@
private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
+ private val _fingerprintSensorLocation = MutableStateFlow<Point?>(null)
+ override val fingerprintSensorLocation: Flow<Point?> = _fingerprintSensorLocation
+
+ private val _faceSensorLocation = MutableStateFlow<Point?>(null)
+ override val faceSensorLocation: Flow<Point?> = _faceSensorLocation
+
+ private val _biometricUnlockSource = MutableStateFlow<BiometricUnlockSource?>(null)
+ override val biometricUnlockSource: Flow<BiometricUnlockSource?> = _biometricUnlockSource
+
override fun isKeyguardShowing(): Boolean {
return _isKeyguardShowing.value
}
@@ -99,6 +120,22 @@
_dozeAmount.value = dozeAmount
}
+ fun setBiometricUnlockState(state: BiometricUnlockModel) {
+ _biometricUnlockState.tryEmit(state)
+ }
+
+ fun setBiometricUnlockSource(source: BiometricUnlockSource?) {
+ _biometricUnlockSource.tryEmit(source)
+ }
+
+ fun setFaceSensorLocation(location: Point?) {
+ _faceSensorLocation.tryEmit(location)
+ }
+
+ fun setFingerprintSensorLocation(location: Point?) {
+ _fingerprintSensorLocation.tryEmit(location)
+ }
+
override fun isUdfpsSupported(): Boolean {
return _isUdfpsSupported.value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
new file mode 100644
index 0000000..7c22604
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.statusbar.LightRevealEffect
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of [LightRevealScrimRepository] */
+class FakeLightRevealScrimRepository : LightRevealScrimRepository {
+
+ private val _revealEffect: MutableStateFlow<LightRevealEffect> =
+ MutableStateFlow(DEFAULT_REVEAL_EFFECT)
+ override val revealEffect = _revealEffect
+
+ fun setRevealEffect(effect: LightRevealEffect) {
+ _revealEffect.tryEmit(effect)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 63448e2..1a893f8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -56,7 +56,8 @@
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.GlobalSettings
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
/**
* Util class to create real implementations of the FooterActions repositories, viewModel and
@@ -65,6 +66,7 @@
class FooterActionsTestUtils(
private val context: Context,
private val testableLooper: TestableLooper,
+ private val scheduler: TestCoroutineScheduler,
) {
/** Enable or disable the user switcher in the settings. */
fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
@@ -105,7 +107,7 @@
foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
broadcastDispatcher: BroadcastDispatcher = mock(),
- bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+ bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
): FooterActionsInteractor {
return FooterActionsInteractorImpl(
activityStarter,
@@ -126,7 +128,7 @@
/** Create a [SecurityRepository] to be used in tests. */
fun securityRepository(
securityController: SecurityController = FakeSecurityController(),
- bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+ bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
): SecurityRepository {
return SecurityRepositoryImpl(
securityController,
@@ -145,7 +147,7 @@
fun userSwitcherRepository(
@Application context: Context = this.context.applicationContext,
bgHandler: Handler = Handler(testableLooper.looper),
- bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+ bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
userManager: UserManager = mock(),
userTracker: UserTracker = FakeUserTracker(),
userSwitcherController: UserSwitcherController = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index a7eadba..0dd1fc7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -66,7 +66,8 @@
_userId = _userInfo.id
_userHandle = UserHandle.of(_userId)
- callbacks.forEach { it.onUserChanged(_userId, userContext) }
+ val copy = callbacks.toList()
+ copy.forEach { it.onUserChanged(_userId, userContext) }
}
fun onProfileChanged() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 94b67ce..598e2b9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -452,13 +452,6 @@
return -1;
}
- if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
- // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
- // ever be invoked when the user is encrypted or lockdown.
- Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
- return -1;
- }
-
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 66992aa..cf72833 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8920,9 +8920,7 @@
}
if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN)
- && getParent().getConfiguration().orientation == ORIENTATION_PORTRAIT
- && getParent().getWindowConfiguration().getWindowingMode()
- == WINDOWING_MODE_FULLSCREEN) {
+ && isParentFullscreenPortrait()) {
// We are using the parent configuration here as this is the most recent one that gets
// passed to onConfigurationChanged when a relevant change takes place
return info.getMinAspectRatio();
@@ -8945,6 +8943,13 @@
return info.getMinAspectRatio();
}
+ private boolean isParentFullscreenPortrait() {
+ final WindowContainer parent = getParent();
+ return parent != null
+ && parent.getConfiguration().orientation == ORIENTATION_PORTRAIT
+ && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ }
+
/**
* Returns true if the activity has maximum or minimum aspect ratio.
*/
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index d345227..cd26e2e 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -226,6 +226,9 @@
}
private void setReady(boolean ready) {
+ if (mReady == ready) {
+ return;
+ }
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready", mSyncId);
mReady = ready;
if (!ready) return;
@@ -239,7 +242,9 @@
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc);
wc.setSyncGroup(this);
wc.prepareSync();
- mWm.mWindowPlacerLocked.requestTraversal();
+ if (mReady) {
+ mWm.mWindowPlacerLocked.requestTraversal();
+ }
}
void onCancelSync(WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b033dca..a32e460 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -341,7 +341,11 @@
if (childArea == null) {
continue;
}
- pw.println(prefix + "* " + childArea.getName());
+ pw.print(prefix + "* " + childArea.getName());
+ if (childArea.isOrganized()) {
+ pw.print(" (organized)");
+ }
+ pw.println();
if (childArea.isTaskDisplayArea()) {
// TaskDisplayArea can only contain task. And it is already printed by display.
continue;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 576296e..b9aeec6 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3458,9 +3458,8 @@
@Override
public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
- super.dump(pw, prefix, dumpAll);
pw.print(prefix);
- pw.println("Display: mDisplayId=" + mDisplayId + " rootTasks=" + getRootTaskCount());
+ pw.println("Display: mDisplayId=" + mDisplayId + (isOrganized() ? " (organized)" : ""));
final String subPrefix = " " + prefix;
pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity);
@@ -3491,6 +3490,7 @@
pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion);
pw.println();
+ super.dump(pw, prefix, dumpAll);
pw.print(prefix); pw.print("mLayoutSeq="); pw.println(mLayoutSeq);
pw.print(" mCurrentFocus="); pw.println(mCurrentFocus);
@@ -3582,6 +3582,7 @@
pw.println();
mInsetsStateController.dump(prefix, pw);
mDwpcHelper.dump(prefix, pw);
+ pw.println();
}
@Override
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2866f42..89cad9c 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3457,7 +3457,6 @@
final DisplayContent display = getChildAt(i);
display.dump(pw, prefix, dumpAll);
}
- pw.println();
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 3949952..14a2d03 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -277,31 +277,29 @@
// is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is
// different, we should recalcuating the bounds.
boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = false;
- // shouldSetAsOverrideWindowingMode is set if the task needs to retain the launchMode
- // regardless of the windowing mode of the parent.
- boolean shouldSetAsOverrideWindowingMode = false;
- if (launchMode == WINDOWING_MODE_PINNED) {
- if (DEBUG) appendLog("picture-in-picture");
- } else if (!root.isResizeable()) {
- if (shouldLaunchUnresizableAppInFreeformInFreeformMode(root, suggestedDisplayArea,
- options)) {
- launchMode = WINDOWING_MODE_UNDEFINED;
- if (outParams.mBounds.isEmpty()) {
- getTaskBounds(root, suggestedDisplayArea, layout, launchMode, hasInitialBounds,
- outParams.mBounds);
- hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = true;
+ if (suggestedDisplayArea.inFreeformWindowingMode()) {
+ if (launchMode == WINDOWING_MODE_PINNED) {
+ if (DEBUG) appendLog("picture-in-picture");
+ } else if (!root.isResizeable()) {
+ if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea, options)) {
+ launchMode = WINDOWING_MODE_FREEFORM;
+ if (outParams.mBounds.isEmpty()) {
+ getTaskBounds(root, suggestedDisplayArea, layout, launchMode,
+ hasInitialBounds, outParams.mBounds);
+ hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = true;
+ }
+ if (DEBUG) appendLog("unresizable-freeform");
+ } else {
+ launchMode = WINDOWING_MODE_FULLSCREEN;
+ outParams.mBounds.setEmpty();
+ if (DEBUG) appendLog("unresizable-forced-maximize");
}
- if (DEBUG) appendLog("unresizable-freeform");
- } else {
- launchMode = WINDOWING_MODE_FULLSCREEN;
- outParams.mBounds.setEmpty();
- shouldSetAsOverrideWindowingMode = true;
- if (DEBUG) appendLog("unresizable-forced-maximize");
}
+ } else {
+ if (DEBUG) appendLog("non-freeform-task-display-area");
}
// If launch mode matches display windowing mode, let it inherit from display.
outParams.mWindowingMode = launchMode == suggestedDisplayArea.getWindowingMode()
- && !shouldSetAsOverrideWindowingMode
? WINDOWING_MODE_UNDEFINED : launchMode;
if (phase == PHASE_WINDOWING_MODE) {
@@ -652,7 +650,7 @@
inOutBounds.offset(xOffset, yOffset);
}
- private boolean shouldLaunchUnresizableAppInFreeformInFreeformMode(ActivityRecord activity,
+ private boolean shouldLaunchUnresizableAppInFreeform(ActivityRecord activity,
TaskDisplayArea displayArea, @Nullable ActivityOptions options) {
if (options != null && options.getLaunchWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
// Do not launch the activity in freeform if it explicitly requested fullscreen mode.
@@ -665,7 +663,8 @@
final int displayOrientation = orientationFromBounds(displayArea.getBounds());
final int activityOrientation = resolveOrientation(activity, displayArea,
displayArea.getBounds());
- if (displayOrientation != activityOrientation) {
+ if (displayArea.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && displayOrientation != activityOrientation) {
return true;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 7ce17d4..ec3962c 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -167,9 +167,9 @@
private SurfaceControl.Transaction mFinishTransaction = null;
/**
- * Contains change infos for both participants and all ancestors. We have to track ancestors
- * because they are all promotion candidates and thus we need their start-states
- * to be captured.
+ * Contains change infos for both participants and all remote-animatable ancestors. The
+ * ancestors can be the promotion candidates so their start-states need to be captured.
+ * @see #getAnimatableParent
*/
final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>();
@@ -228,7 +228,8 @@
}
@Nullable
- static Transition fromBinder(@NonNull IBinder token) {
+ static Transition fromBinder(@Nullable IBinder token) {
+ if (token == null) return null;
try {
return ((Token) token).mTransition.get();
} catch (ClassCastException e) {
@@ -417,8 +418,9 @@
mSyncId, wc);
// "snapshot" all parents (as potential promotion targets). Do this before checking
// if this is already a participant in case it has since been re-parented.
- for (WindowContainer curr = wc.getParent(); curr != null && !mChanges.containsKey(curr);
- curr = curr.getParent()) {
+ for (WindowContainer<?> curr = getAnimatableParent(wc);
+ curr != null && !mChanges.containsKey(curr);
+ curr = getAnimatableParent(curr)) {
mChanges.put(curr, new ChangeInfo(curr));
if (isReadyGroup(curr)) {
mReadyTracker.addGroup(curr);
@@ -943,13 +945,6 @@
cleanUpInternal();
return;
}
- // Ensure that wallpaper visibility is updated with the latest wallpaper target.
- for (int i = mParticipants.size() - 1; i >= 0; --i) {
- final WindowContainer<?> wc = mParticipants.valueAt(i);
- if (isWallpaper(wc) && wc.getDisplayContent() != null) {
- wc.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
- }
- }
mState = STATE_PLAYING;
mStartTransaction = transaction;
@@ -1306,6 +1301,16 @@
return sb.toString();
}
+ /** Returns the parent that the remote animator can animate or control. */
+ private static WindowContainer<?> getAnimatableParent(WindowContainer<?> wc) {
+ WindowContainer<?> parent = wc.getParent();
+ while (parent != null
+ && (!parent.canCreateRemoteAnimationTarget() && !parent.isOrganized())) {
+ parent = parent.getParent();
+ }
+ return parent;
+ }
+
private static boolean reportIfNotTop(WindowContainer wc) {
// Organized tasks need to be reported anyways because Core won't show() their surfaces
// and we can't rely on onTaskAppeared because it isn't in sync.
@@ -1529,7 +1534,8 @@
intermediates.clear();
boolean foundParentInTargets = false;
// Collect the intermediate parents between target and top changed parent.
- for (WindowContainer<?> p = wc.getParent(); p != null; p = p.getParent()) {
+ for (WindowContainer<?> p = getAnimatableParent(wc); p != null;
+ p = getAnimatableParent(p)) {
final ChangeInfo parentChange = changes.get(p);
if (parentChange == null || !parentChange.hasChanged(p)) break;
if (p.mRemoteToken == null) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 908fdbd..920b1ba 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -113,12 +113,6 @@
private boolean mShouldUpdateZoom;
- /**
- * Temporary storage for taking a screenshot of the wallpaper.
- * @see #screenshotWallpaperLocked()
- */
- private WindowState mTmpTopWallpaper;
-
@Nullable private Point mLargestDisplaySize = null;
private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult();
@@ -962,21 +956,16 @@
}
WindowState getTopVisibleWallpaper() {
- mTmpTopWallpaper = null;
-
for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
- token.forAllWindows(w -> {
- final WindowStateAnimator winAnim = w.mWinAnimator;
- if (winAnim != null && winAnim.getShown() && winAnim.mLastAlpha > 0f) {
- mTmpTopWallpaper = w;
- return true;
+ for (int i = token.getChildCount() - 1; i >= 0; i--) {
+ final WindowState w = token.getChildAt(i);
+ if (w.mWinAnimator.getShown() && w.mWinAnimator.mLastAlpha > 0f) {
+ return w;
}
- return false;
- }, true /* traverseTopToBottom */);
+ }
}
-
- return mTmpTopWallpaper;
+ return null;
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 63344a0..68b853d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8878,14 +8878,14 @@
}
@Override
- public List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName) {
+ public List<DisplayInfo> getPossibleDisplayInfo(int displayId) {
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- if (packageName == null || !isRecentsComponent(packageName, callingUid)) {
- Slog.e(TAG, "Unable to verify uid for package " + packageName
- + " for getPossibleMaximumWindowMetrics");
+ if (!mAtmService.isCallerRecents(callingUid)) {
+ Slog.e(TAG, "Unable to verify uid for getPossibleDisplayInfo"
+ + " on uid " + callingUid);
return new ArrayList<>();
}
@@ -8903,31 +8903,6 @@
return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId);
}
- /**
- * Returns {@code true} when the calling package is the recents component.
- */
- boolean isRecentsComponent(@NonNull String callingPackageName, int callingUid) {
- String recentsPackage;
- try {
- String recentsComponent = mContext.getResources().getString(
- R.string.config_recentsComponentName);
- if (recentsComponent == null) {
- return false;
- }
- recentsPackage = ComponentName.unflattenFromString(recentsComponent).getPackageName();
- } catch (Resources.NotFoundException e) {
- Slog.e(TAG, "Unable to verify if recents component", e);
- return false;
- }
- try {
- return callingUid == mContext.getPackageManager().getPackageUid(callingPackageName, 0)
- && callingPackageName.equals(recentsPackage);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "Unable to verify if recents component", e);
- return false;
- }
- }
-
void grantEmbeddedWindowFocus(Session session, IBinder focusToken, boolean grantFocus) {
synchronized (mGlobalLock) {
final EmbeddedWindowController.EmbeddedWindow embeddedWindow =
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index d3aa073..df7b3cd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -74,15 +74,15 @@
int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, mockWC);
- // Make sure a traversal is requested
- verify(mWm.mWindowPlacerLocked, times(1)).requestTraversal();
+ // The traversal is not requested because ready is not set.
+ verify(mWm.mWindowPlacerLocked, times(0)).requestTraversal();
bse.onSurfacePlacement();
verify(listener, times(0)).onTransactionReady(anyInt(), any());
bse.setReady(id);
// Make sure a traversal is requested
- verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal();
+ verify(mWm.mWindowPlacerLocked).requestTraversal();
bse.onSurfacePlacement();
verify(listener, times(1)).onTransactionReady(eq(id), notNull());
@@ -103,14 +103,14 @@
int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, mockWC);
bse.setReady(id);
- // Make sure traversals requested (one for add and another for setReady)
- verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal();
+ // Make sure traversals requested.
+ verify(mWm.mWindowPlacerLocked).requestTraversal();
bse.onSurfacePlacement();
verify(listener, times(0)).onTransactionReady(anyInt(), any());
mockWC.onSyncFinishedDrawing();
- // Make sure a (third) traversal is requested.
- verify(mWm.mWindowPlacerLocked, times(3)).requestTraversal();
+ // Make sure the second traversal is requested.
+ verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal();
bse.onSurfacePlacement();
verify(listener, times(1)).onTransactionReady(eq(id), notNull());
}
@@ -127,8 +127,8 @@
int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, mockWC);
bse.setReady(id);
- // Make sure traversals requested (one for add and another for setReady)
- verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal();
+ // Make sure traversals requested.
+ verify(mWm.mWindowPlacerLocked).requestTraversal();
bse.onSurfacePlacement();
verify(listener, times(0)).onTransactionReady(anyInt(), any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 2a88eb0..1188f49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -825,11 +825,10 @@
}
@Test
- public void testLaunchesPortraitUnresizableOnFreeformLandscapeDisplay() {
+ public void testLaunchesPortraitUnresizableOnFreeformDisplayWithFreeformSizeCompat() {
mAtm.mDevEnableNonResizableMultiWindow = true;
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
- assertTrue(freeformDisplay.getBounds().width() > freeformDisplay.getBounds().height());
final ActivityOptions options = ActivityOptions.makeBasic();
mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
@@ -837,42 +836,12 @@
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder().setOptions(options).calculate());
- assertEquals(WINDOWING_MODE_UNDEFINED, mResult.mWindowingMode);
- }
-
- @Test
- public void testLaunchesLandscapeUnresizableOnFreeformLandscapeDisplay() {
- mAtm.mDevEnableNonResizableMultiWindow = true;
- final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
WINDOWING_MODE_FREEFORM);
- assertTrue(freeformDisplay.getBounds().width() > freeformDisplay.getBounds().height());
- final ActivityOptions options = ActivityOptions.makeBasic();
- mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
- mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
- mActivity.info.screenOrientation = SCREEN_ORIENTATION_LANDSCAPE;
- assertEquals(RESULT_CONTINUE,
- new CalculateRequestBuilder().setOptions(options).calculate());
-
- assertEquals(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode);
}
@Test
- public void testLaunchesUndefinedUnresizableOnFreeformLandscapeDisplay() {
- mAtm.mDevEnableNonResizableMultiWindow = true;
- final TestDisplayContent freeformDisplay = createNewDisplayContent(
- WINDOWING_MODE_FREEFORM);
- assertTrue(freeformDisplay.getBounds().width() > freeformDisplay.getBounds().height());
- final ActivityOptions options = ActivityOptions.makeBasic();
- mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
- mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
- assertEquals(RESULT_CONTINUE,
- new CalculateRequestBuilder().setOptions(options).calculate());
-
- assertEquals(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode);
- }
-
- @Test
- public void testForceMaximizingAppsOnNonFreeformDisplay() {
+ public void testSkipsForceMaximizingAppsOnNonFreeformDisplay() {
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
options.setLaunchBounds(new Rect(0, 0, 200, 100));
@@ -886,9 +855,8 @@
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder().setOptions(options).calculate());
- // Non-resizable apps must be launched in fullscreen in a fullscreen display regardless of
- // other properties.
- assertEquals(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode);
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FULLSCREEN);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 54bcbd9..999523f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -476,6 +476,8 @@
wallpaperWindow.mHasSurface = true;
doReturn(true).when(mDisplayContent).isAttached();
transition.collect(mDisplayContent);
+ assertFalse("The change of non-interesting window container should be skipped",
+ transition.mChanges.containsKey(mDisplayContent.getParent()));
mDisplayContent.getWindowConfiguration().setRotation(
(mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9df4a40..aab70b5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -372,15 +372,6 @@
dc.mTransitionController.finishTransition(transit.getToken());
assertFalse(wallpaperWindow.isVisible());
assertFalse(token.isVisible());
-
- // Assume wallpaper was visible. When transaction is ready without wallpaper target,
- // wallpaper should be requested to be invisible.
- token.setVisibility(true);
- transit = dc.mTransitionController.createTransition(TRANSIT_CLOSE);
- dc.mTransitionController.collect(token);
- transit.onTransactionReady(transit.getSyncId(), t);
- assertFalse(token.isVisibleRequested());
- assertTrue(token.isVisible());
}
private static void prepareSmallerSecondDisplay(DisplayContent dc, int width, int height) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7f8e4ba..741721d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2000,6 +2000,17 @@
"nr_advanced_threshold_bandwidth_khz_int";
/**
+ * Indicating whether to include LTE cell bandwidths when determining whether the aggregated
+ * cell bandwidth meets the required threshold for NR advanced.
+ *
+ * @see TelephonyDisplayInfo#OVERRIDE_NETWORK_TYPE_NR_ADVANCED
+ *
+ * @hide
+ */
+ public static final String KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL =
+ "include_lte_for_nr_advanced_threshold_bandwidth_bool";
+
+ /**
* Boolean indicating if operator name should be shown in the status bar
* @hide
*/
@@ -9001,6 +9012,7 @@
sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0);
+ sDefaults.putBoolean(KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, false);
sDefaults.putIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
new int[]{CARRIER_NR_AVAILABILITY_NSA, CARRIER_NR_AVAILABILITY_SA});
sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);