[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: eab411141b -s ours am: c3f9c6dfdf -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23789021

Change-Id: I786e788017579624789136be4ba3248960013962
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index e23bbc6..d3bde4b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.graphics.ImageFormat;
 import android.hardware.ICameraService;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
@@ -1478,6 +1479,12 @@
             }
         }
 
+        // Allow RAW formats, even when not advertised.
+        if (inputFormat == ImageFormat.RAW_PRIVATE || inputFormat == ImageFormat.RAW10
+                || inputFormat == ImageFormat.RAW12 || inputFormat == ImageFormat.RAW_SENSOR) {
+            return true;
+        }
+
         if (validFormat == false) {
             return false;
         }
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index a95ce64..7f53cb4 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -771,7 +771,7 @@
             Zygote.applyInvokeWithSystemProperty(parsedArgs);
 
             if (Zygote.nativeSupportsMemoryTagging()) {
-                String mode = SystemProperties.get("arm64.memtag.process.system_server", "");
+                String mode = SystemProperties.get("persist.arm64.memtag.system_server", "");
                 if (mode.isEmpty()) {
                   /* The system server has ASYNC MTE by default, in order to allow
                    * system services to specify their own MTE level later, as you
diff --git a/core/res/res/layout/shutdown_dialog.xml b/core/res/res/layout/shutdown_dialog.xml
index ec67aa8..726c255 100644
--- a/core/res/res/layout/shutdown_dialog.xml
+++ b/core/res/res/layout/shutdown_dialog.xml
@@ -40,7 +40,7 @@
         android:fontFamily="@string/config_headlineFontFamily"/>
 
     <TextView
-        android:id="@+id/text2"
+        android:id="@id/text2"
         android:layout_width="wrap_content"
         android:layout_height="32sp"
         android:text="@string/shutdown_progress"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 6a7c065..a941dc7 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometriese hardeware is nie beskikbaar nie"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Stawing is gekanselleer"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nie herken nie"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Gesig word nie herken nie"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Stawing is gekanselleer"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Geen PIN, patroon of wagwoord is gestel nie"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Kon nie staaf nie"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index ecab978b..f97768e 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ባዮሜትራዊ ሃርድዌር አይገኝም"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ማረጋገጥ ተሰርዟል"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"አልታወቀም"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"መልክ አልታወቀም"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ማረጋገጥ ተሰርዟል"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ምንም ፒን፣ ሥርዓተ ጥለት ወይም የይለፍ ቃል አልተቀናበረም"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"ማረጋገጥ ላይ ስህተት"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 87df6bb..a812ac0 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -624,6 +624,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"معدّات المقاييس الحيوية غير متاحة."</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"تم إلغاء المصادقة."</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"لم يتم التعرف عليها."</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"لم يتم التعرّف على الوجه."</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"تم إلغاء المصادقة."</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"لم يتم ضبط رقم تعريف شخصي أو نقش أو كلمة مرور."</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"خطأ في المصادقة"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 2542163..626c3d6 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"বায়োমেট্ৰিক হাৰ্ডৱেৰ উপলব্ধ নহয়"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"বিশ্বাসযোগ্যতাৰ প্ৰমাণীকৰণ বাতিল কৰা হৈছে"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"চিনাক্ত কৰিব পৰা নাই"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"মুখাৱয়ব চিনি পোৱা নাই"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"বিশ্বাসযোগ্যতাৰ প্ৰমাণীকৰণ বাতিল কৰা হৈছে"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"কোনো পিন, আৰ্হি বা পাছৱৰ্ড ছেট কৰা নাই"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"আসোঁৱাহৰ বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ কৰি থকা হৈছে"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 3d7e487..5a41074 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrik proqram əlçatan deyil"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Doğrulama ləğv edildi"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Tanınmır"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Üz tanınmadı"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Doğrulama ləğv edildi"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Pin, nümunə və ya parol ayarlanmayıb"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Doğrulama zamanı xəta baş verdi"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 1e731e8..1baf387 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrijski hardver nije dostupan"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Potvrda identiteta je otkazana"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nije prepoznato"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Lice nije prepoznato"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Potvrda identiteta je otkazana"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Niste podesili ni PIN, ni šablon, ni lozinku"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Greška pri potvrdi identiteta"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index f8b6daa..f226b96 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -622,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Біяметрычнае абсталяванне недаступнае"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Аўтэнтыфікацыя скасавана"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Не распазнана"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Твар не распазнаны"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Аўтэнтыфікацыя скасавана"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Не заданы PIN-код, узор разблакіроўкі або пароль"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Памылка аўтэнтыфікацыі"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 65c883a..b4ba000 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометричният хардуер не е налице"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Удостоверяването бе анулирано"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Не е разпознато"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Лицето не е разпознато"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Удостоверяването бе анулирано"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Няма зададен ПИН код, фигура или парола"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Грешка при удостоверяването"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index fe686db..25c0d42 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"বায়োমেট্রিক হার্ডওয়্যার পাওয়া যাবে না"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"যাচাইকরণ বাতিল হয়েছে"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"স্বীকৃত নয়"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ফেস চেনা যায়নি"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"যাচাইকরণ বাতিল হয়েছে"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"পিন, প্যাটার্ন অথবা পাসওয়ার্ড সেট করা নেই"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"যাচাইকরণে সমস্যা হয়েছে"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 63fff4f..e824ecc 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrijski hardver nije dostupan"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikacija je otkazana"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nije prepoznato"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Lice nije prepoznato"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentifikacija je otkazana"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nije postavljen PIN, uzorak niti lozinka"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Greška pri autentifikaciji"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index b581d49..46fc3a3 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Maquinari biomètric no disponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"S\'ha cancel·lat l\'autenticació"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"No s\'ha reconegut"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"No s\'ha reconegut la cara"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"S\'ha cancel·lat l\'autenticació"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No s\'ha definit cap PIN, patró o contrasenya"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error en l\'autenticació"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index fc97867..196fa56 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -622,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrický hardware není k dispozici"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Ověření bylo zrušeno"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nerozpoznáno"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Obličej nebyl rozpoznán"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Ověření bylo zrušeno"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Není nastaven žádný PIN, gesto ani heslo"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Při ověřování došlo k chybě"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 43f6a8c..c1f0e71 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisk hardware er ikke tilgængelig"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Godkendelsen blev annulleret"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ikke genkendt"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Ansigt blev ikke genkendt"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Godkendelsen blev annulleret"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Der er ikke angivet pinkode, mønster eller adgangskode"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Der opstod fejl i forbindelse med godkendelse"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 55e8455..ad6cfbf 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrische Hardware nicht verfügbar"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentifizierung abgebrochen"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nicht erkannt"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Gesicht nicht erkannt"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentifizierung abgebrochen"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Keine PIN, kein Muster und kein Passwort festgelegt"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Fehler bei der Authentifizierung"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 5f7306a..4ea1eab 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Δεν υπάρχει διαθέσιμος βιομετρικός εξοπλισμός"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Ο έλεγχος ταυτότητας ακυρώθηκε"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Δεν αναγνωρίστηκε"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Το πρόσωπο δεν αναγνωρίστηκε"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Ο έλεγχος ταυτότητας ακυρώθηκε"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Δεν έχει οριστεί PIN, μοτίβο ή κωδικός πρόσβασης"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Σφάλμα κατά τον έλεγχο ταυτότητας"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 73b2968..675add9 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication cancelled"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognised"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Face not recognised"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication cancelled"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern or password set"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error while authenticating"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 73017c1..7b6ccf3 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication canceled"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognized"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Face not recognized"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication canceled"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern, or password set"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error authenticating"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index aeabd00..a1c7872 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication cancelled"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognised"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Face not recognised"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication cancelled"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern or password set"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error while authenticating"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 92f9897..7c467e8 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication cancelled"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognised"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Face not recognised"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication cancelled"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern or password set"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error while authenticating"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index b560c01..4f6c120 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‎‏‏‎‎‎‎‎Biometric hardware unavailable‎‏‎‎‏‎"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎Authentication canceled‎‏‎‎‏‎"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎Not recognized‎‏‎‎‏‎"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‎‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎Face not recognized‎‏‎‎‏‎"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‎‎‏‎‎‎‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‏‎‎Authentication canceled‎‏‎‎‏‎"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎No pin, pattern, or password set‎‏‎‎‏‎"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‏‏‎Error authenticating‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index b39f862..9343060 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"No hay hardware biométrico disponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Se canceló la autenticación"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"No se reconoció"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"No se reconoció el rostro"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Se canceló la autenticación"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No se estableció ningún PIN, patrón ni contraseña"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error de autenticación"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 4b58a6f..c0e705c 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biométrico no disponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticación cancelada"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"No se reconoce"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Cara no reconocida"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autenticación cancelada"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No se ha definido el PIN, el patrón o la contraseña"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"No se ha podido autenticar"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index e4c9948..c50f3ba 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biomeetriline riistvara ei ole saadaval"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentimine tühistati"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ei tuvastatud"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Nägu ei tuvastatud"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentimine tühistati"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN-koodi, mustrit ega parooli pole määratud"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Viga autentimisel"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index a165af1..51f41ef 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrikoa ez dago erabilgarri"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Utzi da autentifikazioa"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ez da ezagutu"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Ez da ezagutu aurpegia"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Utzi egin da autentifikazioa"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Ez da ezarri PIN koderik, eredurik edo pasahitzik"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Errorea autentifikatzean"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 1813fc4..b594258 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"سخت‌افزار زیست‌سنجی دردسترس نیست"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"اصالت‌سنجی لغو شد"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"شناسایی نشد"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"چهره شناسایی نشد"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"اصالت‌سنجی لغو شد"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"پین، الگو یا گذرواژه‌ای تنظیم نشده است"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"خطا هنگام اصالت‌سنجی"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 5f120c5..edfae76 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrinen laitteisto ei käytettävissä"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Todennus peruutettu"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ei tunnistettu"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Kasvoja ei tunnistettu"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Todennus peruutettu"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN-koodia, kuviota tai salasanaa ei ole asetettu"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Virhe todennuksessa"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index e72b41e..bca6087 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Matériel biométrique indisponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentification annulée"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Données biométriques non reconnues"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Visage non reconnu"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentification annulée"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Aucun NIP, schéma ou mot de passe défini"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Erreur d\'authentification"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index c162672..221c526 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Matériel biométrique indisponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentification annulée"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Non reconnue"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Visage non reconnu"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentification annulée"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Aucun code, schéma ni mot de passe n\'est défini"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Erreur d\'authentification"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 18fd292..39ae243 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"O hardware biométrico non está dispoñible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Cancelouse a autenticación"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Non se recoñeceu"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Non se recoñeceu a cara"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Cancelouse a autenticación"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Non se estableceu ningún PIN, padrón ou contrasinal"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Produciuse un erro ao realizar a autenticación"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index ab3859e..703bb44 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"બાયોમેટ્રિક હાર્ડવેર ઉપલબ્ધ નથી"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"પ્રમાણીકરણ રદ કર્યું"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ઓળખાયેલ નથી"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ચહેરો ઓળખાયો નથી"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"પ્રમાણીકરણ રદ કર્યું"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"કોઈ પિન, પૅટર્ન અથવા પાસવર્ડ સેટ કરેલો નથી"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"પ્રમાણિત કરવામાં ભૂલ આવી"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 4c66168..bfa4f39 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"बायोमेट्रिक हार्डवेयर उपलब्ध नहीं है"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"प्रमाणीकरण रद्द किया गया"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"पहचान नहीं हो पाई"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"चेहरा नहीं पहचाना गया"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"प्रमाणीकरण रद्द किया गया"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"पिन, पैटर्न या पासवर्ड सेट नहीं है"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"गड़बड़ी की पुष्टि की जा रही है"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index eb8d111..d655dd4 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrijski hardver nije dostupan"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikacija otkazana"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nije prepoznato"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Lice nije prepoznato"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentifikacija otkazana"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nisu postavljeni PIN, uzorak ni zaporka"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Pogreška prilikom autentifikacije"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 793fc86..e5efd16 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrikus hardver nem áll rendelkezésre"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Hitelesítés megszakítva"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nem ismerhető fel"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Sikertelen arcfelismerés"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Hitelesítés megszakítva"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nem állított be PIN-kódot, mintát vagy jelszót."</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Hiba történt a hitelesítés közben"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index da1b5dc..f831700 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Կենսաչափական սարքը հասանելի չէ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Նույնականացումը չեղարկվեց"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Չհաջողվեց ճանաչել"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Դեմքը չի ճանաչվել"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Նույնականացումը չեղարկվեց"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Ավելացրեք PIN կոդ, նախշ կամ գաղտնաբառ։"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Չհաջողվեց նույնականացնել"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index af5e5fe..92d85c7 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrik tidak tersedia"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentikasi dibatalkan"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Tidak dikenali"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Wajah tidak dikenali"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentikasi dibatalkan"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Tidak ada PIN, pola, atau sandi yang disetel"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error saat mengautentikasi"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index b43935f..1c7f5b3 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Lífkennavélbúnaður ekki tiltækur"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Hætt við auðkenningu"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Þekktist ekki"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Andlit þekkist ekki"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Hætt við auðkenningu"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Ekkert PIN-númer, mynstur eða aðgangsorð stillt"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Villa við auðkenningu"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index f34b103..fe663dd 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrico non disponibile"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticazione annullata"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Non riconosciuto"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Volto non riconosciuto"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autenticazione annullata"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Non hai impostato PIN, sequenza o password"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Errore durante l\'autenticazione"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index cc03284..1bba6e5 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"חומרה ביומטרית לא זמינה"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"האימות בוטל"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"לא זוהתה"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"הפנים לא זוהו"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"האימות בוטל"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"עוד לא הוגדרו קוד אימות, קו ביטול נעילה או סיסמה"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"שגיאה באימות"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index d8d721c..64eb303 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"生体認証ハードウェアが利用できません"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"認証をキャンセルしました"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"認識されませんでした"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"顔を認識できません"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"認証をキャンセルしました"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN、パターン、パスワードが設定されていません"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"エラー認証"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index c7fa7d0..e09aa15 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ბიომეტრიული აპარატურა მიუწვდომელია"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ავტორიზაცია გაუქმდა"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"არ არის ამოცნობილი"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"სახის ამოცნობა ვერ მოხერხდა"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ავტორიზაცია გაუქმდა"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN-კოდი, ნიმუში ან პაროლი დაყენებული არ არის"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"შეცდომა ავთენტიკაციისას"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 29902e9..70f5828 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрикалық жабдық жоқ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Аутентификациядан бас тартылды."</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Танылмады"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Бет танылмады."</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Аутентификациядан бас тартылды."</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Ешқандай PIN коды, өрнек немесе құпия сөз орнатылмаған."</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Аутентификациялауда қате шықты."</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 340135e..8647785 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"មិនអាច​ប្រើឧបករណ៍​ស្កេន​ស្នាមម្រាមដៃ​បានទេ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"បាន​បោះបង់​ការ​ផ្ទៀងផ្ទាត់"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"មិនអាចសម្គាល់បានទេ"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"មិន​ស្គាល់​មុខ"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"បាន​បោះបង់​ការ​ផ្ទៀងផ្ទាត់"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"គ្មាន​ការកំណត់​កូដ pin លំនាំ ឬពាក្យសម្ងាត់​ទេ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"មានបញ្ហាក្នុង​ការផ្ទៀងផ្ទាត់"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 1f0cbfc..26aa63d 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ಬಯೋಮೆಟ್ರಿಕ್ ಹಾರ್ಡ್‌ವೇರ್‌ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ಪ್ರಮಾಣೀಕರಣವನ್ನು ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ಮುಖವನ್ನು ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ಪ್ರಮಾಣೀಕರಣವನ್ನು ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ಪಿನ್, ಪ್ಯಾಟರ್ನ್ ಅಥವಾ ಪಾಸ್‌ವರ್ಡ್ ಸೆಟ್ ಮಾಡಿಲ್ಲ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"ದೃಢೀಕರಿಸುವಾಗ ದೋಷ ಎದುರಾಗಿದೆ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index e6979ae..18fe689 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"생체 인식 하드웨어를 사용할 수 없음"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"인증이 취소되었습니다."</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"인식할 수 없음"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"얼굴을 인식할 수 없습니다."</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"인증이 취소되었습니다."</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN, 패턴, 비밀번호가 설정되지 않음"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"인증 오류"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 80a0c7c..ede68e4 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрикалык аппарат жеткиликсиз"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Аныктыгын текшерүү жокко чыгарылды"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Таанылган жок"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Жүз таанылган жок"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Аныктыгын текшерүү жокко чыгарылды"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN код, графикалык ачкыч же сырсөз коюлган жок"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Аутентификация катасы"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 15517b5..b600d2e 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ຮາດແວຊີວະມິຕິບໍ່ສາມາດໃຊ້ໄດ້"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ຍົກເລີກການຮັບຮອງຄວາມຖືກຕ້ອງແລ້ວ"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ບໍ່ຮັບຮູ້"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ບໍ່ສາມາດຈຳແນກໜ້າໄດ້"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ຍົກເລີກການຮັບຮອງຄວາມຖືກຕ້ອງແລ້ວ"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ບໍ່ໄດ້ຕັ້ງ PIN, ຮູບແບບປົດລັອກ ຫຼື ລະຫັດຜ່ານ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"ເກີດຄວາມຜິດພາດໃນການພິສູດຢືນຢັນ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 6a12170..e3f1952 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -622,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrinė aparatinė įranga nepasiekiama"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikavimas atšauktas"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Neatpažinta"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Veidas neatpažintas"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentifikavimas atšauktas"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nenustatytas PIN kodas, atrakinimo piešinys arba slaptažodis"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Autentifikuojant įvyko klaida"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index fade02e..d874fdd 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisko datu aparatūra nav pieejama"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikācija ir atcelta"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Dati nav atpazīti"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Seja netika atpazīta"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentifikācija ir atcelta"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN, kombinācija vai parole nav iestatīta"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Autentifikācijas kļūda"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 1f2341dd..06a3530 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрискиот хардвер е недостапен"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Проверката е откажана"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Непознат"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Ликот не е препознаен"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Проверката е откажана"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Не е поставен PIN, шема или лозинка"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Грешка при проверката"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index c3b86e3..0ca9b37 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ബയോമെട്രിക് ഹാർ‌ഡ്‌വെയർ ലഭ്യമല്ല"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"പരിശോധിച്ചുറപ്പിക്കൽ റദ്ദാക്കി"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"തിരിച്ചറിഞ്ഞില്ല"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"മുഖം തിരിച്ചറിഞ്ഞിട്ടില്ല"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"പരിശോധിച്ചുറപ്പിക്കൽ റദ്ദാക്കി"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"പിന്നോ പാറ്റേണോ പാസ്‌വേഡോ സജ്ജീകരിച്ചിട്ടില്ല"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"പിശക് പരിശോധിച്ചുറപ്പിക്കുന്നു"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index b69fb83..8df63ec 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрийн техник хангамж боломжгүй байна"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Нотолгоог цуцаллаа"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Таниагүй"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Царайг таньсангүй"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Нотолгоог цуцаллаа"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Тохируулсан пин, хээ эсвэл нууц үг алга"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Баталгаажуулахад алдаа гарлаа"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 5f6b35d..432ffce 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"बायोमेट्रिक हार्डवेअर उपलब्ध नाही"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ऑथेंटिकेशन रद्द केले"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ओळखले नाही"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"चेहरा ओळखता आला नाही"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ऑथेंटिकेशन रद्द केले"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"कोणताही पिन, पॅटर्न किंवा पासवर्ड सेट केलेला नाही"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"एरर ऑथेंटिकेट करत आहे"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index f62c706..ec737c9 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Perkakasan biometrik tidak tersedia"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Pengesahan dibatalkan"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Tidak dikenali"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Wajah tidak dikenali"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Pengesahan dibatalkan"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Pin, corak atau kata laluan tidak ditetapkan"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Ralat semasa membuat pengesahan"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 5a84957..2df17b4 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ဇီဝအချက်အလက်သုံး ကွန်ပျူတာစက်ပစ္စည်း မရရှိနိုင်ပါ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်လိုက်သည်"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"မသိ"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"မျက်နှာကို မသိရှိပါ"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်လိုက်သည်"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ပင်နံပါတ်၊ လော့ခ်ပုံစံ သို့မဟုတ် စကားဝှက် သတ်မှတ်မထားပါ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"အထောက်အထားစိစစ်ရာတွင် အမှားအယွင်းရှိနေသည်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 15ef328..f8c8aaa 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisk maskinvare er utilgjengelig"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentiseringen er avbrutt"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ikke gjenkjent"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Gjenkjenner ikke ansiktet"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentiseringen er avbrutt"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN-kode, mønster eller passord er ikke angitt"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Feil under autentiseringen"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 92ee0a1..bbb1992 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"बायोमेट्रिक हार्डवेयर उपलब्ध छैन"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"प्रमाणीकरण रद्द गरियो"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"पहिचान भएन"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"अनुहार पहिचान गर्न सकिएन"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"प्रमाणीकरण रद्द गरियो"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"कुनै पनि PIN, ढाँचा वा पासवर्ड सेट गरिएको छैन"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"प्रमाणित गर्ने क्रममा त्रुटि भयो"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 80082c8..5457872 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrische hardware niet beschikbaar"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Verificatie geannuleerd"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Niet herkend"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Gezicht niet herkend"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Verificatie geannuleerd"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Geen pincode, patroon of wachtwoord ingesteld"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Fout bij verificatie"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 81a7bb4..6932b44 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ବାୟୋମେଟ୍ରିକ୍‌ ହାର୍ଡୱେର୍‌ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ପ୍ରାମାଣିକତାକୁ ବାତିଲ୍ କରାଯାଇଛି"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ଚିହ୍ନଟ ହେଲାନାହିଁ"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ଫେସ ଚିହ୍ନଟ କରାଯାଇନାହିଁ"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ପ୍ରାମାଣିକତାକୁ ବାତିଲ୍ କରାଯାଇଛି"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"କୌଣସି ପିନ୍, ପେଟେର୍ନ ବା ପାସ୍‍ୱର୍ଡ ସେଟ୍ ନାହିଁ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"ପ୍ରାମାଣିକରଣ କରିବା ସମୟରେ ତ୍ରୁଟି"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 9c61e5b..0d0e0c8 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ਬਾਇਓਮੈਟ੍ਰਿਕ ਹਾਰਡਵੇਅਰ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ਪ੍ਰਮਾਣੀਕਰਨ ਰੱਦ ਕੀਤਾ ਗਿਆ"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ਚਿਹਰੇ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ਪ੍ਰਮਾਣੀਕਰਨ ਰੱਦ ਕੀਤਾ ਗਿਆ"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ਕੋਈ ਪਿੰਨ, ਪੈਟਰਨ ਜਾਂ ਪਾਸਵਰਡ ਸੈੱਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"ਗੜਬੜ ਨੂੰ ਪ੍ਰਮਾਣਿਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 7a8684f4..f72ff68 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -622,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Sprzęt biometryczny niedostępny"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Anulowano uwierzytelnianie"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nie rozpoznano"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Nie rozpoznano twarzy"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Anulowano uwierzytelnianie"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nie ustawiono kodu PIN, wzoru ani hasła"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Podczas uwierzytelniania wystąpił błąd"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 0a6f769..72cb00e 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biométrico indisponível"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticação cancelada"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Não reconhecido"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Rosto não reconhecido"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autenticação cancelada"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nenhum PIN, padrão ou senha configurado"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Erro na autenticação"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 96242e1..2158dfd 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biométrico indisponível."</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticação cancelada"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Não reconhecido."</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Rosto não reconhecido"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autenticação cancelada"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nenhum PIN, padrão ou palavra-passe definidos."</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Erro ao autenticar."</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 0a6f769..72cb00e 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biométrico indisponível"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticação cancelada"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Não reconhecido"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Rosto não reconhecido"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autenticação cancelada"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nenhum PIN, padrão ou senha configurado"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Erro na autenticação"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 5edccb4..b52c0eb 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometric indisponibil"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentificarea a fost anulată"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nu este recunoscut"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Fața nu a fost recunoscută"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentificarea a fost anulată"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nu este setat un cod PIN, un model sau o parolă"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Eroare la autentificare"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index e48c487..f8f323f 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -622,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрическое оборудование недоступно"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Аутентификация отменена"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Не распознано"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Лицо не распознано."</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Аутентификация отменена"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Укажите PIN-код, пароль или графический ключ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Ошибка аутентификации."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 0df5c65..a62caeb 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ජීවමිතික දෘඪාංග ලබා ගත නොහැකිය"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"සත්‍යාපනය අවලංගු කළා"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"හඳුනා නොගන්නා ලදී"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"මුහුණ හඳුනා නොගන්නා ලදි"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"සත්‍යාපනය අවලංගු කළා"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"රහස් අංක, රටා, හෝ මුරපද කිසිවක් සකසා නැත"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"සත්‍යාපනය කිරීමේ දෝෂයකි"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index f13ef2b..9c3c655 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -622,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrický hardvér nie je k dispozícii"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Overenie bolo zrušené"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nerozpoznané"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Tvár nebola rozpoznaná"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Overenie bolo zrušené"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nie je nastavený PIN, vzor ani heslo"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Chyba overenia"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 4647ed9..f92a5bd 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -622,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Strojna oprema za biometrične podatke ni na voljo"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Preverjanje pristnosti je preklicano"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ni prepoznano"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Obraz ni prepoznan"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Preverjanje pristnosti je preklicano"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nastavljena ni nobena koda PIN, vzorec ali geslo"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Napaka pri preverjanju pristnosti"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 82d92c1..512dbc7 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Nuk ofrohet harduer biometrik"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Vërtetimi u anulua"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nuk njihet"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Fytyra nuk njihet"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Vërtetimi u anulua"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nuk është vendosur kod PIN, motiv ose fjalëkalim"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Gabim gjatë vërtetimit"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index ef2456e..ccd638e 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -621,6 +621,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометријски хардвер није доступан"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Потврда идентитета је отказана"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Није препознато"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Лице није препознато"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Потврда идентитета је отказана"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Нисте подесили ни PIN, ни шаблон, ни лозинку"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Грешка при потврди идентитета"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index c40b205..4b5d45e 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisk maskinvara är inte tillgänglig"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentiseringen avbröts"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Identifierades inte"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Ansiktet känns inte igen"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentiseringen avbröts"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Pinkod, mönster eller lösenord har inte angetts"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Ett fel uppstod vid autentiseringen"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index caf09cb..5e30e0d 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Maunzi ya bayometriki hayapatikani"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Imeghairi uthibitishaji"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Hayatambuliki"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Imeshindwa kutambua uso"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Imeghairi uthibitishaji"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Hujaweka pin, mchoro au nenosiri"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Hitilafu imetokea wakati wa uthibitishaji"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 4c765ed..26442bf 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"பயோமெட்ரிக் வன்பொருள் இல்லை"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"அங்கீகரிப்பு ரத்தானது"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"அடையாளங்காணபடவில்லை"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"முகத்தைக் கண்டறிய முடியவில்லை"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"அங்கீகரிப்பு ரத்தானது"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"பின்னோ, பேட்டர்னோ, கடவுச்சொல்லோ அமைக்கப்படவில்லை"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"அங்கீகரிப்பதில் பிழை"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 8946886..73aab58 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"బయోమెట్రిక్ హార్డ్‌వేర్‌ అందుబాటులో లేదు"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ప్రమాణీకరణ రద్దు చేయబడింది"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"గుర్తించలేదు"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ముఖం గుర్తించబడలేదు"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ప్రమాణీకరణ రద్దు చేయబడింది"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"పిన్, ఆకృతి లేదా పాస్‌వర్డ్‌ సెట్ చేయబడలేదు"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"ప్రామాణీకరిస్తున్నప్పుడు ఎర్రర్ ఏర్పడింది"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 107a66a..916e857 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ฮาร์ดแวร์ไบโอเมตริกไม่พร้อมใช้งาน"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ยกเลิกการตรวจสอบสิทธิ์แล้ว"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ไม่รู้จัก"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ไม่รู้จักใบหน้า"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ยกเลิกการตรวจสอบสิทธิ์แล้ว"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ไม่ได้ตั้ง PIN, รูปแบบ หรือรหัสผ่าน"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"การตรวจสอบข้อผิดพลาด"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 549882b..786c324 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Walang biometric hardware"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Nakansela ang pag-authenticate"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Hindi nakilala"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Hindi nakilala ang mukha"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Nakansela ang pag-authenticate"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Walang itinakdang pin, pattern, o password"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Nagkaroon ng error sa pag-authenticate"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 33b66d3..a0c71b5 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biyometrik donanım kullanılamıyor"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Kimlik doğrulama iptal edildi"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Tanınmadı"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Yüz tanınmadı"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Kimlik doğrulama iptal edildi"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN, desen veya şifre seti yok"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Kimlik doğrulama sırasında hata oluştu"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 5dd6037..0c9f99a 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -622,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Біометричне апаратне забезпечення недоступне"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Автентифікацію скасовано"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Не розпізнано"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Обличчя не розпізнано"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Автентифікацію скасовано"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Не вказано PIN-код, ключ або пароль"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Помилка автентифікації"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index e692be7..e1c2752 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"بایومیٹرک ہارڈ ویئر دستیاب نہیں ہے"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"تصدیق کا عمل منسوخ ہو گیا"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"تسلیم شدہ نہیں ہے"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"چہرے کی شناخت نہیں ہو سکی"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"تصدیق کا عمل منسوخ ہو گیا"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"کوئی پن، پیٹرن، یا پاس ورڈ سیٹ نہیں ہے"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"خرابی کی توثیق ہو رہی ہے"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index bb9cbd2..93ad0ce 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrik sensor ishlamayapti"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikatsiya bekor qilindi"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Aniqlanmadi"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Yuz aniqlanmadi"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentifikatsiya bekor qilindi"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN kod, grafik kalit yoki parol sozlanmagan"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Autentifikatsiya amalga oshmadi"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 83730d5..bfe1561 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Không có phần cứng sinh trắc học"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Đã hủy xác thực"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Không nhận dạng được"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Không nhận dạng được khuôn mặt"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Đã hủy xác thực"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Chưa đặt mã PIN, hình mở khóa hoặc mật khẩu"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Lỗi khi xác thực"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 8055813..d7101f4 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"生物识别硬件无法使用"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"身份验证已取消"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"无法识别"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"无法识别面孔"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"身份验证已取消"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"未设置任何 PIN 码、图案和密码"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"进行身份验证时出错"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 3055600..a471a42 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"無法使用生物識別硬件"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"已取消驗證"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"未能識別"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"無法辨識面孔"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"已取消驗證"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"未設定 PIN、圖案或密碼"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"驗證時發生錯誤"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index f26cbe8..1449ab6 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"無法使用生物特徵辨識硬體"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"已取消驗證"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"無法辨識"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"無法辨識臉孔"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"已取消驗證"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"未設定 PIN 碼、解鎖圖案或密碼"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"驗證時發生錯誤"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 270cc4b..72b4404 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -620,6 +620,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"I-Biometric hardware ayitholakali"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Ukufakazela ubuqiniso kukhanseliwe"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Akwaziwa"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Ubuso abaziwa"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Ukufakazela ubuqiniso kukhanseliwe"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Ayikho iphinikhodi, iphethini, noma iphasiwedi esethiwe"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Iphutha lokufakazela ubuqiniso"</string>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index fd74185..4ae54a0 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -168,8 +168,9 @@
     <bool name="ignore_emergency_number_routing_from_db">false</bool>
     <java-symbol type="bool" name="ignore_emergency_number_routing_from_db" />
 
-    <!-- Whether "Virtual DSDA", i.e. in-call IMS connectivity can be provided on both subs with
-         only single logical modem, by using its data connection in addition to cellular IMS. -->
-    <bool name="config_enable_virtual_dsda">false</bool>
-    <java-symbol type="bool" name="config_enable_virtual_dsda" />
+    <!-- Boolean indicating whether allow sending null to modem to clear the previous initial attach
+         data profile -->
+    <bool name="allow_clear_initial_attach_data_profile">false</bool>
+    <java-symbol type="bool" name="allow_clear_initial_attach_data_profile" />
+
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a5b2b85..04fef58 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1789,6 +1789,8 @@
     <string name="biometric_error_user_canceled">Authentication canceled</string>
     <!-- Message shown by the biometric dialog when biometric is not recognized -->
     <string name="biometric_not_recognized">Not recognized</string>
+    <!-- Message shown by the biometric dialog when face is not recognized [CHAR LIMIT=50] -->
+    <string name="biometric_face_not_recognized">Face not recognized</string>
     <!-- Message shown when biometric authentication has been canceled [CHAR LIMIT=50] -->
     <string name="biometric_error_canceled">Authentication canceled</string>
     <!-- Message returned to applications if BiometricPrompt setAllowDeviceCredentials is enabled but no pin, pattern, or password is set. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dc4eafd..80a9977 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2570,6 +2570,7 @@
   <java-symbol type="string" name="biometric_error_hw_unavailable" />
   <java-symbol type="string" name="biometric_error_user_canceled" />
   <java-symbol type="string" name="biometric_not_recognized" />
+  <java-symbol type="string" name="biometric_face_not_recognized" />
   <java-symbol type="string" name="biometric_error_canceled" />
   <java-symbol type="string" name="biometric_error_device_not_secured" />
   <java-symbol type="string" name="biometric_error_generic" />
@@ -5020,6 +5021,7 @@
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
 
+
   <java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
   <java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
   <java-symbol name="materialColorSurfaceContainerLowest" type="attr"/>
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
index c9d9b57..5b7899b 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.drawer;
 
-/** Interface for {@link SwitchController} whose instances support dynamic summary */
+/** Interface for {@link EntryController} whose instances support dynamic summary */
 public interface DynamicSummary {
     /** @return the dynamic summary text */
     String getDynamicSummary();
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
index af711dd..cb15773 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.drawer;
 
-/** Interface for {@link SwitchController} whose instances support dynamic title */
+/** Interface for {@link EntryController} whose instances support dynamic title */
 public interface DynamicTitle {
     /** @return the dynamic title text */
     String getDynamicTitle();
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java
new file mode 100644
index 0000000..1c14c0a
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.drawer;
+
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An abstract class for injecting entries to Settings.
+ */
+public abstract class EntriesProvider extends ContentProvider {
+    private static final String TAG = "EntriesProvider";
+
+    public static final String METHOD_GET_ENTRY_DATA = "getEntryData";
+    public static final String METHOD_GET_PROVIDER_ICON = "getProviderIcon";
+    public static final String METHOD_GET_DYNAMIC_TITLE = "getDynamicTitle";
+    public static final String METHOD_GET_DYNAMIC_SUMMARY = "getDynamicSummary";
+    public static final String METHOD_IS_CHECKED = "isChecked";
+    public static final String METHOD_ON_CHECKED_CHANGED = "onCheckedChanged";
+
+    /**
+     * @deprecated use {@link #METHOD_GET_ENTRY_DATA} instead.
+     */
+    @Deprecated
+    public static final String METHOD_GET_SWITCH_DATA = "getSwitchData";
+
+    public static final String EXTRA_ENTRY_DATA = "entry_data";
+    public static final String EXTRA_SWITCH_CHECKED_STATE = "checked_state";
+    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR = "set_checked_error";
+    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE = "set_checked_error_message";
+
+    /**
+     * @deprecated use {@link #EXTRA_ENTRY_DATA} instead.
+     */
+    @Deprecated
+    public static final String EXTRA_SWITCH_DATA = "switch_data";
+
+    private String mAuthority;
+    private final Map<String, EntryController> mControllerMap = new LinkedHashMap<>();
+    private final List<Bundle> mEntryDataList = new ArrayList<>();
+
+    /**
+     * Get a list of {@link EntryController} for this provider.
+     */
+    protected abstract List<? extends EntryController> createEntryControllers();
+
+    protected EntryController getController(String key) {
+        return mControllerMap.get(key);
+    }
+
+    @Override
+    public void attachInfo(Context context, ProviderInfo info) {
+        mAuthority = info.authority;
+        Log.i(TAG, mAuthority);
+        super.attachInfo(context, info);
+    }
+
+    @Override
+    public boolean onCreate() {
+        final List<? extends EntryController> controllers = createEntryControllers();
+        if (controllers == null || controllers.isEmpty()) {
+            throw new IllegalArgumentException();
+        }
+
+        for (EntryController controller : controllers) {
+            final String key = controller.getKey();
+            if (TextUtils.isEmpty(key)) {
+                throw new NullPointerException("Entry key cannot be null: "
+                        + controller.getClass().getSimpleName());
+            } else if (mControllerMap.containsKey(key)) {
+                throw new IllegalArgumentException("Entry key " + key + " is duplicated by: "
+                        + controller.getClass().getSimpleName());
+            }
+
+            controller.setAuthority(mAuthority);
+            mControllerMap.put(key, controller);
+            if (!(controller instanceof PrimarySwitchController)) {
+                mEntryDataList.add(controller.getBundle());
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public Bundle call(String method, String uriString, Bundle extras) {
+        final Bundle bundle = new Bundle();
+        final String key = extras != null
+                ? extras.getString(META_DATA_PREFERENCE_KEYHINT)
+                : null;
+        if (TextUtils.isEmpty(key)) {
+            switch (method) {
+                case METHOD_GET_ENTRY_DATA:
+                    bundle.putParcelableList(EXTRA_ENTRY_DATA, mEntryDataList);
+                    return bundle;
+                case METHOD_GET_SWITCH_DATA:
+                    bundle.putParcelableList(EXTRA_SWITCH_DATA, mEntryDataList);
+                    return bundle;
+                default:
+                    return null;
+            }
+        }
+
+        final EntryController controller = mControllerMap.get(key);
+        if (controller == null) {
+            return null;
+        }
+
+        switch (method) {
+            case METHOD_GET_ENTRY_DATA:
+            case METHOD_GET_SWITCH_DATA:
+                if (!(controller instanceof PrimarySwitchController)) {
+                    return controller.getBundle();
+                }
+                break;
+            case METHOD_GET_PROVIDER_ICON:
+                if (controller instanceof ProviderIcon) {
+                    return ((ProviderIcon) controller).getProviderIcon();
+                }
+                break;
+            case METHOD_GET_DYNAMIC_TITLE:
+                if (controller instanceof DynamicTitle) {
+                    bundle.putString(META_DATA_PREFERENCE_TITLE,
+                            ((DynamicTitle) controller).getDynamicTitle());
+                    return bundle;
+                }
+                break;
+            case METHOD_GET_DYNAMIC_SUMMARY:
+                if (controller instanceof DynamicSummary) {
+                    bundle.putString(META_DATA_PREFERENCE_SUMMARY,
+                            ((DynamicSummary) controller).getDynamicSummary());
+                    return bundle;
+                }
+                break;
+            case METHOD_IS_CHECKED:
+                if (controller instanceof ProviderSwitch) {
+                    bundle.putBoolean(EXTRA_SWITCH_CHECKED_STATE,
+                            ((ProviderSwitch) controller).isSwitchChecked());
+                    return bundle;
+                }
+                break;
+            case METHOD_ON_CHECKED_CHANGED:
+                if (controller instanceof ProviderSwitch) {
+                    return onSwitchCheckedChanged(extras.getBoolean(EXTRA_SWITCH_CHECKED_STATE),
+                            (ProviderSwitch) controller);
+                }
+                break;
+        }
+        return null;
+    }
+
+    private Bundle onSwitchCheckedChanged(boolean checked, ProviderSwitch controller) {
+        final boolean success = controller.onSwitchCheckedChanged(checked);
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, !success);
+        if (success) {
+            if (controller instanceof DynamicSummary) {
+                ((EntryController) controller).notifySummaryChanged(getContext());
+            }
+        } else {
+            bundle.putString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE,
+                    controller.getSwitchErrorMessage(checked));
+        }
+        return bundle;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+}
+
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java
new file mode 100644
index 0000000..5d6e6a3
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.drawer;
+
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY;
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_TITLE;
+import static com.android.settingslib.drawer.TileUtils.EXTRA_CATEGORY_KEY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;
+
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
+/**
+ * A controller that manages events for switch.
+ */
+public abstract class EntryController {
+
+    private String mAuthority;
+
+    /**
+     * Returns the key for this switch.
+     */
+    public abstract String getKey();
+
+    /**
+     * Returns the {@link MetaData} for this switch.
+     */
+    protected abstract MetaData getMetaData();
+
+    /**
+     * Notify registered observers that title was updated and attempt to sync changes.
+     */
+    public void notifyTitleChanged(Context context) {
+        if (this instanceof DynamicTitle) {
+            notifyChanged(context, METHOD_GET_DYNAMIC_TITLE);
+        }
+    }
+
+    /**
+     * Notify registered observers that summary was updated and attempt to sync changes.
+     */
+    public void notifySummaryChanged(Context context) {
+        if (this instanceof DynamicSummary) {
+            notifyChanged(context, METHOD_GET_DYNAMIC_SUMMARY);
+        }
+    }
+
+    void setAuthority(String authority) {
+        mAuthority = authority;
+    }
+
+    Bundle getBundle() {
+        final MetaData metaData = getMetaData();
+        if (metaData == null) {
+            throw new NullPointerException("Should not return null in getMetaData()");
+        }
+
+        final Bundle bundle = metaData.build();
+        final String uriString = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(mAuthority)
+                .build()
+                .toString();
+        bundle.putString(META_DATA_PREFERENCE_KEYHINT, getKey());
+        if (this instanceof ProviderIcon) {
+            bundle.putString(META_DATA_PREFERENCE_ICON_URI, uriString);
+        }
+        if (this instanceof DynamicTitle) {
+            bundle.putString(META_DATA_PREFERENCE_TITLE_URI, uriString);
+        }
+        if (this instanceof DynamicSummary) {
+            bundle.putString(META_DATA_PREFERENCE_SUMMARY_URI, uriString);
+        }
+        if (this instanceof ProviderSwitch) {
+            bundle.putString(META_DATA_PREFERENCE_SWITCH_URI, uriString);
+        }
+        return bundle;
+    }
+
+    private void notifyChanged(Context context, String method) {
+        final Uri uri = TileUtils.buildUri(mAuthority, method, getKey());
+        context.getContentResolver().notifyChange(uri, null);
+    }
+
+    /**
+     * Collects all meta data of the item.
+     */
+    protected static class MetaData {
+        private String mCategory;
+        private int mOrder;
+        @DrawableRes
+        private int mIcon;
+        private int mIconBackgroundHint;
+        private int mIconBackgroundArgb;
+        private Boolean mIconTintable;
+        @StringRes
+        private int mTitleId;
+        private String mTitle;
+        @StringRes
+        private int mSummaryId;
+        private String mSummary;
+        private PendingIntent mPendingIntent;
+
+        /**
+         * @param category the category of the switch. This value must be from {@link CategoryKey}.
+         */
+        public MetaData(@NonNull String category) {
+            mCategory = category;
+        }
+
+        /**
+         * Set the order of the item that should be displayed on screen. Bigger value items displays
+         * closer on top.
+         */
+        public MetaData setOrder(int order) {
+            mOrder = order;
+            return this;
+        }
+
+        /** Set the icon that should be displayed for the item. */
+        public MetaData setIcon(@DrawableRes int icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /** Set the icon background color. The value may or may not be used by Settings app. */
+        public MetaData setIconBackgoundHint(int hint) {
+            mIconBackgroundHint = hint;
+            return this;
+        }
+
+        /** Set the icon background color as raw ARGB. */
+        public MetaData setIconBackgoundArgb(int argb) {
+            mIconBackgroundArgb = argb;
+            return this;
+        }
+
+        /** Specify whether the icon is tintable. */
+        public MetaData setIconTintable(boolean tintable) {
+            mIconTintable = tintable;
+            return this;
+        }
+
+        /** Set the title that should be displayed for the item. */
+        public MetaData setTitle(@StringRes int id) {
+            mTitleId = id;
+            return this;
+        }
+
+        /** Set the title that should be displayed for the item. */
+        public MetaData setTitle(String title) {
+            mTitle = title;
+            return this;
+        }
+
+        /** Set the summary text that should be displayed for the item. */
+        public MetaData setSummary(@StringRes int id) {
+            mSummaryId = id;
+            return this;
+        }
+
+        /** Set the summary text that should be displayed for the item. */
+        public MetaData setSummary(String summary) {
+            mSummary = summary;
+            return this;
+        }
+
+        public MetaData setPendingIntent(PendingIntent pendingIntent) {
+            mPendingIntent = pendingIntent;
+            return this;
+        }
+
+        protected Bundle build() {
+            final Bundle bundle = new Bundle();
+            bundle.putString(EXTRA_CATEGORY_KEY, mCategory);
+
+            if (mOrder != 0) {
+                bundle.putInt(META_DATA_KEY_ORDER, mOrder);
+            }
+
+            if (mIcon != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_ICON, mIcon);
+            }
+            if (mIconBackgroundHint != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, mIconBackgroundHint);
+            }
+            if (mIconBackgroundArgb != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, mIconBackgroundArgb);
+            }
+            if (mIconTintable != null) {
+                bundle.putBoolean(META_DATA_PREFERENCE_ICON_TINTABLE, mIconTintable);
+            }
+
+            if (mTitleId != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_TITLE, mTitleId);
+            } else if (mTitle != null) {
+                bundle.putString(META_DATA_PREFERENCE_TITLE, mTitle);
+            }
+
+            if (mSummaryId != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_SUMMARY, mSummaryId);
+            } else if (mSummary != null) {
+                bundle.putString(META_DATA_PREFERENCE_SUMMARY, mSummary);
+            }
+
+            if (mPendingIntent != null) {
+                bundle.putParcelable(META_DATA_PREFERENCE_PENDING_INTENT, mPendingIntent);
+            }
+
+            return bundle;
+        }
+    }
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
index 2945d5c1..3aa6fcb 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
@@ -19,7 +19,7 @@
 import android.os.Bundle;
 
 /**
- *  Interface for {@link SwitchController} whose instances support icon provided from the content
+ *  Interface for {@link EntryController} whose instances support icon provided from the content
  *  provider
  */
 public interface ProviderIcon {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java
new file mode 100644
index 0000000..47eb31c
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.drawer;
+
+/**
+ *  Interface for {@link EntryController} whose instances support switch widget provided from the
+ *  content provider
+ */
+public interface ProviderSwitch {
+    /**
+     * Returns the checked state of this switch.
+     */
+    boolean isSwitchChecked();
+
+    /**
+     * Called when the checked state of this switch is changed.
+     *
+     * @return true if the checked state was successfully changed, otherwise false
+     */
+    boolean onSwitchCheckedChanged(boolean checked);
+
+    /**
+     * Returns the error message which will be toasted when {@link #onSwitchCheckedChanged} returns
+     * false.
+     */
+    String getSwitchErrorMessage(boolean attemptedChecked);
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
index 54da585..b775e93 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
@@ -75,7 +75,7 @@
             if (infoList != null && !infoList.isEmpty()) {
                 final ProviderInfo providerInfo = infoList.get(0).providerInfo;
                 mComponentInfo = providerInfo;
-                setMetaData(TileUtils.getSwitchDataFromProvider(context, providerInfo.authority,
+                setMetaData(TileUtils.getEntryDataFromProvider(context, providerInfo.authority,
                         mKey));
             } else {
                 Log.e(TAG, "Cannot find package info for "
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
index 23669b2..a1a4e58 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
@@ -16,38 +16,16 @@
 
 package com.android.settingslib.drawer;
 
-import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY;
-import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_TITLE;
-import static com.android.settingslib.drawer.SwitchesProvider.METHOD_IS_CHECKED;
-import static com.android.settingslib.drawer.TileUtils.EXTRA_CATEGORY_KEY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
 
 /**
  * A controller that manages events for switch.
+ *
+ * @deprecated Use {@link EntriesProvider} with {@link ProviderSwitch} instead.
  */
-public abstract class SwitchController {
+@Deprecated
+public abstract class SwitchController extends EntryController implements ProviderSwitch {
 
-    private String mAuthority;
 
     /**
      * Returns the key for this switch.
@@ -55,11 +33,6 @@
     public abstract String getSwitchKey();
 
     /**
-     * Returns the {@link MetaData} for this switch.
-     */
-    protected abstract MetaData getMetaData();
-
-    /**
      * Returns the checked state of this switch.
      */
     protected abstract boolean isChecked();
@@ -76,181 +49,41 @@
      */
     protected abstract String getErrorMessage(boolean attemptedChecked);
 
-    /**
-     * Notify registered observers that title was updated and attempt to sync changes.
-     */
-    public void notifyTitleChanged(Context context) {
-        if (this instanceof DynamicTitle) {
-            notifyChanged(context, METHOD_GET_DYNAMIC_TITLE);
-        }
+    @Override
+    public String getKey() {
+        return getSwitchKey();
+    }
+
+    @Override
+    public boolean isSwitchChecked() {
+        return isChecked();
+    }
+
+    @Override
+    public boolean onSwitchCheckedChanged(boolean checked) {
+        return onCheckedChanged(checked);
+    }
+
+    @Override
+    public String getSwitchErrorMessage(boolean attemptedChecked) {
+        return getErrorMessage(attemptedChecked);
     }
 
     /**
-     * Notify registered observers that summary was updated and attempt to sync changes.
+     * Same as {@link EntryController.MetaData}, for backwards compatibility purpose.
+     *
+     * @deprecated Use {@link EntryController.MetaData} instead.
      */
-    public void notifySummaryChanged(Context context) {
-        if (this instanceof DynamicSummary) {
-            notifyChanged(context, METHOD_GET_DYNAMIC_SUMMARY);
-        }
-    }
-
-    /**
-     * Notify registered observers that checked state was updated and attempt to sync changes.
-     */
-    public void notifyCheckedChanged(Context context) {
-        notifyChanged(context, METHOD_IS_CHECKED);
-    }
-
-    void setAuthority(String authority) {
-        mAuthority = authority;
-    }
-
-    Bundle getBundle() {
-        final MetaData metaData = getMetaData();
-        if (metaData == null) {
-            throw new NullPointerException("Should not return null in getMetaData()");
-        }
-
-        final Bundle bundle = metaData.build();
-        final String uriString = new Uri.Builder()
-                .scheme(ContentResolver.SCHEME_CONTENT)
-                .authority(mAuthority)
-                .build()
-                .toString();
-        bundle.putString(META_DATA_PREFERENCE_KEYHINT, getSwitchKey());
-        bundle.putString(META_DATA_PREFERENCE_SWITCH_URI, uriString);
-        if (this instanceof ProviderIcon) {
-            bundle.putString(META_DATA_PREFERENCE_ICON_URI, uriString);
-        }
-        if (this instanceof DynamicTitle) {
-            bundle.putString(META_DATA_PREFERENCE_TITLE_URI, uriString);
-        }
-        if (this instanceof DynamicSummary) {
-            bundle.putString(META_DATA_PREFERENCE_SUMMARY_URI, uriString);
-        }
-        return bundle;
-    }
-
-    private void notifyChanged(Context context, String method) {
-        final Uri uri = TileUtils.buildUri(mAuthority, method, getSwitchKey());
-        context.getContentResolver().notifyChange(uri, null);
-    }
-
-    /**
-     * Collects all meta data of the item.
-     */
-    protected static class MetaData {
-        private String mCategory;
-        private int mOrder;
-        @DrawableRes
-        private int mIcon;
-        private int mIconBackgroundHint;
-        private int mIconBackgroundArgb;
-        private Boolean mIconTintable;
-        @StringRes
-        private int mTitleId;
-        private String mTitle;
-        @StringRes
-        private int mSummaryId;
-        private String mSummary;
-
+    @Deprecated
+    protected static class MetaData extends EntryController.MetaData {
         /**
          * @param category the category of the switch. This value must be from {@link CategoryKey}.
+         *
+         * @deprecated Use {@link EntryController.MetaData} instead.
          */
+        @Deprecated
         public MetaData(@NonNull String category) {
-            mCategory = category;
-        }
-
-        /**
-         * Set the order of the item that should be displayed on screen. Bigger value items displays
-         * closer on top.
-         */
-        public MetaData setOrder(int order) {
-            mOrder = order;
-            return this;
-        }
-
-        /** Set the icon that should be displayed for the item. */
-        public MetaData setIcon(@DrawableRes int icon) {
-            mIcon = icon;
-            return this;
-        }
-
-        /** Set the icon background color. The value may or may not be used by Settings app. */
-        public MetaData setIconBackgoundHint(int hint) {
-            mIconBackgroundHint = hint;
-            return this;
-        }
-
-        /** Set the icon background color as raw ARGB. */
-        public MetaData setIconBackgoundArgb(int argb) {
-            mIconBackgroundArgb = argb;
-            return this;
-        }
-
-        /** Specify whether the icon is tintable. */
-        public MetaData setIconTintable(boolean tintable) {
-            mIconTintable = tintable;
-            return this;
-        }
-
-        /** Set the title that should be displayed for the item. */
-        public MetaData setTitle(@StringRes int id) {
-            mTitleId = id;
-            return this;
-        }
-
-        /** Set the title that should be displayed for the item. */
-        public MetaData setTitle(String title) {
-            mTitle = title;
-            return this;
-        }
-
-        /** Set the summary text that should be displayed for the item. */
-        public MetaData setSummary(@StringRes int id) {
-            mSummaryId = id;
-            return this;
-        }
-
-        /** Set the summary text that should be displayed for the item. */
-        public MetaData setSummary(String summary) {
-            mSummary = summary;
-            return this;
-        }
-
-        private Bundle build() {
-            final Bundle bundle = new Bundle();
-            bundle.putString(EXTRA_CATEGORY_KEY, mCategory);
-
-            if (mOrder != 0) {
-                bundle.putInt(META_DATA_KEY_ORDER, mOrder);
-            }
-
-            if (mIcon != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_ICON, mIcon);
-            }
-            if (mIconBackgroundHint != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, mIconBackgroundHint);
-            }
-            if (mIconBackgroundArgb != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, mIconBackgroundArgb);
-            }
-            if (mIconTintable != null) {
-                bundle.putBoolean(META_DATA_PREFERENCE_ICON_TINTABLE, mIconTintable);
-            }
-
-            if (mTitleId != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_TITLE, mTitleId);
-            } else if (mTitle != null) {
-                bundle.putString(META_DATA_PREFERENCE_TITLE, mTitle);
-            }
-
-            if (mSummaryId != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_SUMMARY, mSummaryId);
-            } else if (mSummary != null) {
-                bundle.putString(META_DATA_PREFERENCE_SUMMARY, mSummary);
-            }
-            return bundle;
+            super(category);
         }
     }
 }
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
index f2b3e30..ad00ced 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
@@ -16,46 +16,15 @@
 
 package com.android.settingslib.drawer;
 
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.ProviderInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * An abstract class for injecting switches to Settings.
+ *
+ * @deprecated Use {@link EntriesProvider} instead.
  */
-public abstract class SwitchesProvider extends ContentProvider {
-    private static final String TAG = "SwitchesProvider";
-
-    public static final String METHOD_GET_SWITCH_DATA = "getSwitchData";
-    public static final String METHOD_GET_PROVIDER_ICON = "getProviderIcon";
-    public static final String METHOD_GET_DYNAMIC_TITLE = "getDynamicTitle";
-    public static final String METHOD_GET_DYNAMIC_SUMMARY = "getDynamicSummary";
-    public static final String METHOD_IS_CHECKED = "isChecked";
-    public static final String METHOD_ON_CHECKED_CHANGED = "onCheckedChanged";
-
-    public static final String EXTRA_SWITCH_DATA = "switch_data";
-    public static final String EXTRA_SWITCH_CHECKED_STATE = "checked_state";
-    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR = "set_checked_error";
-    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE = "set_checked_error_message";
-
-    private String mAuthority;
-    private final Map<String, SwitchController> mControllerMap = new LinkedHashMap<>();
-    private final List<Bundle> mSwitchDataList = new ArrayList<>();
+@Deprecated
+public abstract class SwitchesProvider extends EntriesProvider {
 
     /**
      * Get a list of {@link SwitchController} for this provider.
@@ -63,129 +32,7 @@
     protected abstract List<SwitchController> createSwitchControllers();
 
     @Override
-    public void attachInfo(Context context, ProviderInfo info) {
-        mAuthority = info.authority;
-        Log.i(TAG, mAuthority);
-        super.attachInfo(context, info);
-    }
-
-    @Override
-    public boolean onCreate() {
-        final List<SwitchController> controllers = createSwitchControllers();
-        if (controllers == null || controllers.isEmpty()) {
-            throw new IllegalArgumentException();
-        }
-
-        controllers.forEach(controller -> {
-            final String key = controller.getSwitchKey();
-            if (TextUtils.isEmpty(key)) {
-                throw new NullPointerException("Switch key cannot be null: "
-                        + controller.getClass().getSimpleName());
-            } else if (mControllerMap.containsKey(key)) {
-                throw new IllegalArgumentException("Switch key " + key + " is duplicated by: "
-                        + controller.getClass().getSimpleName());
-            }
-
-            controller.setAuthority(mAuthority);
-            mControllerMap.put(key, controller);
-            if (!(controller instanceof PrimarySwitchController)) {
-                mSwitchDataList.add(controller.getBundle());
-            }
-        });
-        return true;
-    }
-
-    @Override
-    public Bundle call(String method, String uriString, Bundle extras) {
-        final Bundle bundle = new Bundle();
-        final String key = extras != null
-                ? extras.getString(META_DATA_PREFERENCE_KEYHINT)
-                : null;
-        if (TextUtils.isEmpty(key)) {
-            if (METHOD_GET_SWITCH_DATA.equals(method)) {
-                bundle.putParcelableList(EXTRA_SWITCH_DATA, mSwitchDataList);
-                return bundle;
-            }
-            return null;
-        }
-
-        final SwitchController controller = mControllerMap.get(key);
-        if (controller == null) {
-            return null;
-        }
-
-        switch (method) {
-            case METHOD_GET_SWITCH_DATA:
-                if (!(controller instanceof PrimarySwitchController)) {
-                    return controller.getBundle();
-                }
-                break;
-            case METHOD_GET_PROVIDER_ICON:
-                if (controller instanceof ProviderIcon) {
-                    return ((ProviderIcon) controller).getProviderIcon();
-                }
-                break;
-            case METHOD_GET_DYNAMIC_TITLE:
-                if (controller instanceof DynamicTitle) {
-                    bundle.putString(META_DATA_PREFERENCE_TITLE,
-                            ((DynamicTitle) controller).getDynamicTitle());
-                    return bundle;
-                }
-                break;
-            case METHOD_GET_DYNAMIC_SUMMARY:
-                if (controller instanceof DynamicSummary) {
-                    bundle.putString(META_DATA_PREFERENCE_SUMMARY,
-                            ((DynamicSummary) controller).getDynamicSummary());
-                    return bundle;
-                }
-                break;
-            case METHOD_IS_CHECKED:
-                bundle.putBoolean(EXTRA_SWITCH_CHECKED_STATE, controller.isChecked());
-                return bundle;
-            case METHOD_ON_CHECKED_CHANGED:
-                return onCheckedChanged(extras.getBoolean(EXTRA_SWITCH_CHECKED_STATE), controller);
-        }
-        return null;
-    }
-
-    private Bundle onCheckedChanged(boolean checked, SwitchController controller) {
-        final boolean success = controller.onCheckedChanged(checked);
-        final Bundle bundle = new Bundle();
-        bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, !success);
-        if (success) {
-            if (controller instanceof DynamicSummary) {
-                controller.notifySummaryChanged(getContext());
-            }
-        } else {
-            bundle.putString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE,
-                    controller.getErrorMessage(checked));
-        }
-        return bundle;
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues values) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException();
+    protected List<? extends EntryController> createEntryControllers() {
+        return createSwitchControllers();
     }
 }
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index a0c8ac4..00dd8cc 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -19,6 +19,7 @@
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_NEW_TASK;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
@@ -29,6 +30,7 @@
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ComponentInfo;
@@ -47,6 +49,7 @@
 
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.HashMap;
 
 /**
  * Description of a single dashboard tile that the user can select.
@@ -60,6 +63,8 @@
      */
     public ArrayList<UserHandle> userHandle = new ArrayList<>();
 
+    public HashMap<UserHandle, PendingIntent> pendingIntentMap = new HashMap<>();
+
     @VisibleForTesting
     long mLastUpdateTime;
     private final String mComponentPackage;
@@ -186,6 +191,13 @@
     }
 
     /**
+     * Check whether tile has a pending intent.
+     */
+    public boolean hasPendingIntent() {
+        return !pendingIntentMap.isEmpty();
+    }
+
+    /**
      * Title of the tile that is shown to the user.
      */
     public CharSequence getTitle(Context context) {
@@ -395,6 +407,76 @@
         return TextUtils.equals(profile, PROFILE_PRIMARY);
     }
 
+    /**
+     * Returns whether the tile belongs to another group / category.
+     */
+    public boolean hasGroupKey() {
+        return mMetaData != null
+                && !TextUtils.isEmpty(mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY));
+    }
+
+    /**
+     * Returns the group / category key this tile belongs to.
+     */
+    public String getGroupKey() {
+        return (mMetaData == null) ? null : mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY);
+    }
+
+    /**
+     * The type of the tile.
+     */
+    public enum Type {
+        /**
+         * A preference that can be tapped on to open a new page.
+         */
+        ACTION,
+
+        /**
+         * A preference that can be tapped on to open an external app.
+         */
+        EXTERNAL_ACTION,
+
+        /**
+         * A preference that shows an on / off switch that can be toggled by the user.
+         */
+        SWITCH,
+
+        /**
+         * A preference with both an on / off switch, and a tappable area that can perform an
+         * action.
+         */
+        SWITCH_WITH_ACTION,
+
+        /**
+         * A preference category with a title that can be used to group multiple preferences
+         * together.
+         */
+        GROUP;
+    }
+
+    /**
+     * Returns the type of the tile.
+     *
+     * @see Type
+     */
+    public Type getType() {
+        boolean hasExternalAction = hasPendingIntent();
+        boolean hasAction = hasExternalAction || this instanceof ActivityTile;
+        boolean hasSwitch = hasSwitch();
+
+        if (hasSwitch && hasAction) {
+            return Type.SWITCH_WITH_ACTION;
+        } else if (hasSwitch) {
+            return Type.SWITCH;
+        } else if (hasExternalAction) {
+            return Type.EXTERNAL_ACTION;
+        } else if (hasAction) {
+            return Type.ACTION;
+        } else {
+            return Type.GROUP;
+        }
+    }
+
     public static final Comparator<Tile> TILE_COMPARATOR =
             (lhs, rhs) -> rhs.getOrder() - lhs.getOrder();
 }
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index acc0087..e46db75 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -113,6 +113,12 @@
     public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";
 
     /**
+     * Name of the meta-data item that can be set in the AndroidManifest.xml or in the content
+     * provider to specify the key of a group / category where this preference belongs to.
+     */
+    public static final String META_DATA_PREFERENCE_GROUP_KEY = "com.android.settings.group_key";
+
+    /**
      * Order of the item that should be displayed on screen. Bigger value items displays closer on
      * top.
      */
@@ -202,6 +208,13 @@
             "com.android.settings.switch_uri";
 
     /**
+     * Name of the meta-data item that can be set from the content provider providing the intent
+     * that will be executed when the user taps on the preference.
+     */
+    public static final String META_DATA_PREFERENCE_PENDING_INTENT =
+            "com.android.settings.pending_intent";
+
+    /**
      * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile,
      * the app will always be run in the primary profile.
      *
@@ -331,12 +344,12 @@
                 continue;
             }
             final ProviderInfo providerInfo = resolved.providerInfo;
-            final List<Bundle> switchData = getSwitchDataFromProvider(context,
+            final List<Bundle> entryData = getEntryDataFromProvider(context,
                     providerInfo.authority);
-            if (switchData == null || switchData.isEmpty()) {
+            if (entryData == null || entryData.isEmpty()) {
                 continue;
             }
-            for (Bundle metaData : switchData) {
+            for (Bundle metaData : entryData) {
                 loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
                         providerInfo);
             }
@@ -386,27 +399,43 @@
         if (!tile.userHandle.contains(user)) {
             tile.userHandle.add(user);
         }
+        if (metaData.containsKey(META_DATA_PREFERENCE_PENDING_INTENT)) {
+            tile.pendingIntentMap.put(
+                    user, metaData.getParcelable(META_DATA_PREFERENCE_PENDING_INTENT));
+        }
         if (!outTiles.contains(tile)) {
             outTiles.add(tile);
         }
     }
 
-    /** Returns the switch data of the key specified from the provider */
+    /** Returns the entry data of the key specified from the provider */
     // TODO(b/144732809): rearrange methods by access level modifiers
-    static Bundle getSwitchDataFromProvider(Context context, String authority, String key) {
+    static Bundle getEntryDataFromProvider(Context context, String authority, String key) {
         final Map<String, IContentProvider> providerMap = new ArrayMap<>();
-        final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA, key);
-        return getBundleFromUri(context, uri, providerMap, null /* bundle */);
+        final Uri uri = buildUri(authority, EntriesProvider.METHOD_GET_ENTRY_DATA, key);
+        Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
+        if (result == null) {
+            Uri fallbackUri = buildUri(authority, EntriesProvider.METHOD_GET_SWITCH_DATA, key);
+            result = getBundleFromUri(context, fallbackUri, providerMap, null /* bundle */);
+        }
+        return result;
     }
 
-    /** Returns all switch data from the provider */
-    private static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
+    /** Returns all entry data from the provider */
+    private static List<Bundle> getEntryDataFromProvider(Context context, String authority) {
         final Map<String, IContentProvider> providerMap = new ArrayMap<>();
-        final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA);
+        final Uri uri = buildUri(authority, EntriesProvider.METHOD_GET_ENTRY_DATA);
         final Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
-        return result != null
-                ? result.getParcelableArrayList(SwitchesProvider.EXTRA_SWITCH_DATA)
-                : null;
+        if (result != null) {
+            return result.getParcelableArrayList(EntriesProvider.EXTRA_ENTRY_DATA);
+        } else {
+            Uri fallbackUri = buildUri(authority, EntriesProvider.METHOD_GET_SWITCH_DATA);
+            Bundle fallbackResult =
+                    getBundleFromUri(context, fallbackUri, providerMap, null /* bundle */);
+            return fallbackResult != null
+                    ? fallbackResult.getParcelableArrayList(EntriesProvider.EXTRA_SWITCH_DATA)
+                    : null;
+        }
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 09abc39..9ee8a32 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -209,8 +209,7 @@
         }
         final ComponentName cn = intent.getComponent();
         final String key = cn != null ? cn.flattenToString() : intent.getAction();
-        return logSettingsTileClick(key + (isWorkProfile ? "/work" : "/personal"),
-                sourceMetricsCategory);
+        return logSettingsTileClickWithProfile(key, sourceMetricsCategory, isWorkProfile);
     }
 
     /**
@@ -226,4 +225,20 @@
         clicked(sourceMetricsCategory, logKey);
         return true;
     }
+
+    /**
+     * Logs an event when the setting key is clicked with a specific profile from Profile select
+     * dialog.
+     *
+     * @return true if the key is loggable, otherwise false
+     */
+    public boolean logSettingsTileClickWithProfile(String logKey, int sourceMetricsCategory,
+            boolean isWorkProfile) {
+        if (TextUtils.isEmpty(logKey)) {
+            // Not loggable
+            return false;
+        }
+        clicked(sourceMetricsCategory, logKey + (isWorkProfile ? "/work" : "/personal"));
+        return true;
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index 3352d86..dd8d54a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -203,4 +203,24 @@
         assertThat(loggable).isFalse();
         verifyNoMoreInteractions(mLogWriter);
     }
+
+    @Test
+    public void logSettingsTileClickWithProfile_isPersonalProfile_shouldTagPersonal() {
+        final String key = "abc";
+        final boolean loggable = mProvider.logSettingsTileClickWithProfile(key,
+                MetricsEvent.SETTINGS_GESTURES, false);
+
+        assertThat(loggable).isTrue();
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "abc/personal");
+    }
+
+    @Test
+    public void logSettingsTileClickWithProfile_isWorkProfile_shouldTagWork() {
+        final String key = "abc";
+        final boolean loggable = mProvider.logSettingsTileClickWithProfile(key,
+                MetricsEvent.SETTINGS_GESTURES, true);
+
+        assertThat(loggable).isTrue();
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "abc/work");
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
index aa6b0bf..4d2b1ae 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
@@ -17,19 +17,23 @@
 
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -191,4 +195,65 @@
 
         assertThat(tile.getTitle(RuntimeEnvironment.application)).isNull();
     }
+
+    @Test
+    public void hasPendingIntent_empty_returnsFalse() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.hasPendingIntent()).isFalse();
+    }
+
+    @Test
+    public void hasPendingIntent_notEmpty_returnsTrue() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+        tile.pendingIntentMap.put(
+                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+        assertThat(tile.hasPendingIntent()).isTrue();
+    }
+
+    @Test
+    public void hasGroupKey_empty_returnsFalse() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.hasGroupKey()).isFalse();
+    }
+
+    @Test
+    public void hasGroupKey_notEmpty_returnsTrue() {
+        mActivityInfo.metaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.hasGroupKey()).isTrue();
+    }
+
+    @Test
+    public void getGroupKey_empty_returnsNull() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.getGroupKey()).isNull();
+    }
+
+    @Test
+    public void getGroupKey_notEmpty_returnsValue() {
+        mActivityInfo.metaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.getGroupKey()).isEqualTo("test_key");
+    }
+
+    @Test
+    public void getType_withoutSwitch_returnsAction() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.ACTION);
+    }
+
+    @Test
+    public void getType_withSwitch_returnsSwitchWithAction() {
+        mActivityInfo.metaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH_WITH_ACTION);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java
new file mode 100644
index 0000000..a248330
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.drawer;
+
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_ENTRY_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_CHECKED_STATE;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_DYNAMIC_SUMMARY;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_DYNAMIC_TITLE;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_ENTRY_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_PROVIDER_ICON;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_SWITCH_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_IS_CHECKED;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_ON_CHECKED_CHANGED;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ProviderInfo;
+import android.os.Bundle;
+
+import com.android.settingslib.drawer.EntryController.MetaData;
+import com.android.settingslib.drawer.PrimarySwitchControllerTest.TestPrimarySwitchController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class EntriesProviderTest {
+
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+    private Context mContext;
+    private ProviderInfo mProviderInfo;
+
+    private TestEntriesProvider mEntriesProvider;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mEntriesProvider = new TestEntriesProvider();
+        mProviderInfo = new ProviderInfo();
+        mProviderInfo.authority = "auth";
+    }
+
+    @Test
+    public void attachInfo_noController_shouldThrowIllegalArgumentException() {
+        thrown.expect(IllegalArgumentException.class);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void attachInfo_NoKeyInController_shouldThrowNullPointerException() {
+        thrown.expect(NullPointerException.class);
+        final TestEntryController controller = new TestEntryController();
+        mEntriesProvider.addController(controller);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void attachInfo_NoMetaDataInController_shouldThrowNullPointerException() {
+        thrown.expect(NullPointerException.class);
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        mEntriesProvider.addController(controller);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void attachInfo_duplicateKey_shouldThrowIllegalArgumentException() {
+        thrown.expect(IllegalArgumentException.class);
+        final TestEntryController controller1 = new TestEntryController();
+        final TestEntryController controller2 = new TestEntryController();
+        controller1.setKey("123");
+        controller2.setKey("123");
+        controller1.setMetaData(new MetaData("category"));
+        controller2.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller1);
+        mEntriesProvider.addController(controller2);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void attachInfo_hasDifferentControllers_shouldNotThrowException() {
+        final TestEntryController controller1 = new TestEntryController();
+        final TestEntryController controller2 = new TestEntryController();
+        controller1.setKey("123");
+        controller2.setKey("456");
+        controller1.setMetaData(new MetaData("category"));
+        controller2.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller1);
+        mEntriesProvider.addController(controller2);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void getEntryData_shouldNotReturnPrimarySwitchData() {
+        final EntryController controller = new TestPrimarySwitchController("123");
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle switchData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri",
+                null /* extras*/);
+
+        final ArrayList<Bundle> dataList = switchData.getParcelableArrayList(EXTRA_ENTRY_DATA);
+        assertThat(dataList).isEmpty();
+    }
+
+    @Test
+    public void getEntryData_shouldReturnDataList() {
+        final TestEntryController controller = new TestEntryController();
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category").setPendingIntent(pendingIntent));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle entryData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri",
+                null /* extras*/);
+
+        final ArrayList<Bundle> dataList = entryData.getParcelableArrayList(EXTRA_ENTRY_DATA);
+        assertThat(dataList).hasSize(1);
+        assertThat(dataList.get(0).getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+        assertThat(dataList.get(0).getParcelable(META_DATA_PREFERENCE_PENDING_INTENT,
+                PendingIntent.class))
+                .isEqualTo(pendingIntent);
+    }
+
+    @Test
+    public void getSwitchData_shouldReturnDataList() {
+        final TestEntryController controller = new TestEntryController();
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category").setPendingIntent(pendingIntent));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle entryData = mEntriesProvider.call(METHOD_GET_SWITCH_DATA, "uri",
+                null /* extras*/);
+
+        final ArrayList<Bundle> dataList = entryData.getParcelableArrayList(EXTRA_SWITCH_DATA);
+        assertThat(dataList).hasSize(1);
+        assertThat(dataList.get(0).getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+        assertThat(dataList.get(0).getParcelable(META_DATA_PREFERENCE_PENDING_INTENT,
+                PendingIntent.class))
+                .isEqualTo(pendingIntent);
+    }
+
+    @Test
+    public void getEntryDataByKey_shouldReturnData() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle entryData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri", extras);
+
+        assertThat(entryData.getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+    }
+
+    @Test
+    public void getSwitchDataByKey_shouldReturnData() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle entryData = mEntriesProvider.call(METHOD_GET_SWITCH_DATA, "uri", extras);
+
+        assertThat(entryData.getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+    }
+
+    @Test
+    public void isSwitchChecked_shouldReturnCheckedState() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestSwitchController controller = new TestSwitchController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        controller.setSwitchChecked(true);
+        Bundle result = mEntriesProvider.call(METHOD_IS_CHECKED, "uri", extras);
+
+        assertThat(result.getBoolean(EXTRA_SWITCH_CHECKED_STATE)).isTrue();
+
+        controller.setSwitchChecked(false);
+        result = mEntriesProvider.call(METHOD_IS_CHECKED, "uri", extras);
+
+        assertThat(result.getBoolean(EXTRA_SWITCH_CHECKED_STATE)).isFalse();
+    }
+
+    @Test
+    public void getProviderIcon_noImplementInterface_shouldReturnNull() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle iconBundle = mEntriesProvider.call(METHOD_GET_PROVIDER_ICON, "uri", extras);
+
+        assertThat(iconBundle).isNull();
+    }
+
+    @Test
+    public void getProviderIcon_implementInterface_shouldReturnIcon() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestDynamicController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle iconBundle = mEntriesProvider.call(METHOD_GET_PROVIDER_ICON, "uri", extras);
+
+        assertThat(iconBundle).isEqualTo(TestDynamicController.ICON_BUNDLE);
+    }
+
+    @Test
+    public void getDynamicTitle_noImplementInterface_shouldReturnNull() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_TITLE, "uri", extras);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void getDynamicTitle_implementInterface_shouldReturnTitle() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestDynamicController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_TITLE, "uri", extras);
+
+        assertThat(result.getString(META_DATA_PREFERENCE_TITLE))
+                .isEqualTo(TestDynamicController.TITLE);
+    }
+
+    @Test
+    public void getDynamicSummary_noImplementInterface_shouldReturnNull() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_SUMMARY, "uri", extras);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void getDynamicSummary_implementInterface_shouldReturnSummary() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestDynamicController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_SUMMARY, "uri", extras);
+
+        assertThat(result.getString(META_DATA_PREFERENCE_SUMMARY))
+                .isEqualTo(TestDynamicController.SUMMARY);
+    }
+
+    @Test
+    public void onSwitchCheckedChangedSuccess_shouldReturnNoError() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestSwitchController controller = new TestSwitchController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_ON_CHECKED_CHANGED, "uri", extras);
+
+        assertThat(result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR)).isFalse();
+    }
+
+    @Test
+    public void onSwitchCheckedChangedFailed_shouldReturnErrorMessage() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestSwitchController controller = new TestSwitchController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        controller.setSwitchErrorMessage("error");
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_ON_CHECKED_CHANGED, "uri", extras);
+
+        assertThat(result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR)).isTrue();
+        assertThat(result.getString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE)).isEqualTo("error");
+    }
+
+    private static class TestEntriesProvider extends EntriesProvider {
+
+        private List<EntryController> mControllers;
+
+        @Override
+        protected List<EntryController> createEntryControllers() {
+            return mControllers;
+        }
+
+        void addController(EntryController controller) {
+            if (mControllers == null) {
+                mControllers = new ArrayList<>();
+            }
+            mControllers.add(controller);
+        }
+    }
+
+    private static class TestEntryController extends EntryController {
+
+        private String mKey;
+        private MetaData mMetaData;
+
+        @Override
+        public String getKey() {
+            return mKey;
+        }
+
+        @Override
+        protected MetaData getMetaData() {
+            return mMetaData;
+        }
+
+        void setKey(String key) {
+            mKey = key;
+        }
+
+        void setMetaData(MetaData metaData) {
+            mMetaData = metaData;
+        }
+    }
+
+    private static class TestSwitchController extends EntryController implements ProviderSwitch {
+
+        private String mKey;
+        private MetaData mMetaData;
+        private boolean mChecked;
+        private String mErrorMsg;
+
+        @Override
+        public String getKey() {
+            return mKey;
+        }
+
+        @Override
+        protected MetaData getMetaData() {
+            return mMetaData;
+        }
+
+        @Override
+        public boolean isSwitchChecked() {
+            return mChecked;
+        }
+
+        @Override
+        public boolean onSwitchCheckedChanged(boolean checked) {
+            return mErrorMsg == null ? true : false;
+        }
+
+        @Override
+        public String getSwitchErrorMessage(boolean attemptedChecked) {
+            return mErrorMsg;
+        }
+
+        void setKey(String key) {
+            mKey = key;
+        }
+
+        void setMetaData(MetaData metaData) {
+            mMetaData = metaData;
+        }
+
+        void setSwitchChecked(boolean checked) {
+            mChecked = checked;
+        }
+
+        void setSwitchErrorMessage(String errorMsg) {
+            mErrorMsg = errorMsg;
+        }
+    }
+
+    private static class TestDynamicController extends TestEntryController
+            implements ProviderIcon, DynamicTitle, DynamicSummary {
+
+        static final String TITLE = "title";
+        static final String SUMMARY = "summary";
+        static final Bundle ICON_BUNDLE = new Bundle();
+
+        @Override
+        public Bundle getProviderIcon() {
+            return ICON_BUNDLE;
+        }
+
+        @Override
+        public String getDynamicTitle() {
+            return TITLE;
+        }
+
+        @Override
+        public String getDynamicSummary() {
+            return SUMMARY;
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
index abfb407..80f9efb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
@@ -17,20 +17,24 @@
 
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -173,13 +177,93 @@
         assertThat(tile.mLastUpdateTime).isNotEqualTo(staleTimeStamp);
     }
 
+    @Test
+    public void hasPendingIntent_empty_returnsFalse() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.hasPendingIntent()).isFalse();
+    }
+
+    @Test
+    public void hasPendingIntent_notEmpty_returnsTrue() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+        tile.pendingIntentMap.put(
+                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+        assertThat(tile.hasPendingIntent()).isTrue();
+    }
+
+    @Test
+    public void hasGroupKey_empty_returnsFalse() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.hasGroupKey()).isFalse();
+    }
+
+    @Test
+    public void hasGroupKey_notEmpty_returnsTrue() {
+        mMetaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.hasGroupKey()).isTrue();
+    }
+
+    @Test
+    public void getGroupKey_empty_returnsNull() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.getGroupKey()).isNull();
+    }
+
+    @Test
+    public void getGroupKey_notEmpty_returnsValue() {
+        mMetaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.getGroupKey()).isEqualTo("test_key");
+    }
+
+    @Test
+    public void getType_withSwitch_returnsSwitch() {
+        mMetaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH);
+    }
+
+    @Test
+    public void getType_withSwitchAndPendingIntent_returnsSwitchWithAction() {
+        mMetaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+        tile.pendingIntentMap.put(
+                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH_WITH_ACTION);
+    }
+
+    @Test
+    public void getType_withPendingIntent_returnsExternalAction() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+        tile.pendingIntentMap.put(
+                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.EXTERNAL_ACTION);
+    }
+
+    @Test
+    public void getType_withoutSwitchAndPendingIntent_returnsGroup() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.GROUP);
+    }
+
     @Implements(TileUtils.class)
     private static class ShadowTileUtils {
 
         private static Bundle sMetaData;
 
         @Implementation
-        protected static Bundle getSwitchDataFromProvider(Context context, String authority,
+        protected static Bundle getEntryDataFromProvider(Context context, String authority,
                 String key) {
             return sMetaData;
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 906e06e..2086466 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -21,6 +21,7 @@
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
@@ -40,6 +41,7 @@
 import static org.robolectric.RuntimeEnvironment.application;
 
 import android.app.ActivityManager;
+import android.app.PendingIntent;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -350,6 +352,53 @@
         assertThat(outTiles).isEmpty();
     }
 
+    @Test
+    public void loadTilesForAction_multipleUserProfiles_updatesUserHandle() {
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+                URI_GET_SUMMARY, null, 123, PROFILE_ALL);
+        info.add(resolveInfo);
+
+        when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        TileUtils.loadTilesForAction(mContext, UserHandle.CURRENT, IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+        TileUtils.loadTilesForAction(mContext, new UserHandle(10), IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+
+        assertThat(outTiles).hasSize(1);
+        assertThat(outTiles.get(0).userHandle)
+                .containsExactly(UserHandle.CURRENT, new UserHandle(10));
+    }
+
+    @Test
+    public void loadTilesForAction_withPendingIntent_updatesPendingIntentMap() {
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+                URI_GET_SUMMARY, null, 123, PROFILE_ALL);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        resolveInfo.activityInfo.metaData
+                .putParcelable(META_DATA_PREFERENCE_PENDING_INTENT, pendingIntent);
+        info.add(resolveInfo);
+
+        when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        TileUtils.loadTilesForAction(mContext, UserHandle.CURRENT, IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+        TileUtils.loadTilesForAction(mContext, new UserHandle(10), IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+
+        assertThat(outTiles).hasSize(1);
+        assertThat(outTiles.get(0).pendingIntentMap).containsExactly(
+                UserHandle.CURRENT, pendingIntent, new UserHandle(10), pendingIntent);
+    }
+
     public static ResolveInfo newInfo(boolean systemApp, String category) {
         return newInfo(systemApp, category, null);
     }
@@ -424,7 +473,7 @@
         private static Bundle sMetaData;
 
         @Implementation
-        protected static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
+        protected static List<Bundle> getEntryDataFromProvider(Context context, String authority) {
             return Arrays.asList(sMetaData);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 7f70685..d8348ed 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -374,7 +374,6 @@
         if (Utils.isBiometricAllowed(config.mPromptInfo)) {
             mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
                     config.mPromptInfo,
-                    config.mRequireConfirmation,
                     config.mUserId,
                     config.mOperationId,
                     new BiometricModalities(fpProps, faceProps));
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 57f1928..5b746f1b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -1048,6 +1048,18 @@
         return false;
     }
 
+    private String getNotRecognizedString(@Modality int modality) {
+        final int messageRes;
+        final int userId = mCurrentDialogArgs.argi1;
+        if (isFaceAuthEnrolled(userId) && isFingerprintEnrolled(userId)) {
+            messageRes = modality == TYPE_FACE
+                    ? R.string.biometric_face_not_recognized
+                    : R.string.fingerprint_error_not_match;
+        } else {
+            messageRes = R.string.biometric_not_recognized;
+        }
+        return mContext.getString(messageRes);
+    }
 
     private String getErrorString(@Modality int modality, int error, int vendorCode) {
         switch (modality) {
@@ -1094,7 +1106,7 @@
                 mCurrentDialog.animateToCredentialUI();
             } else if (isSoftError) {
                 final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED)
-                        ? mContext.getString(R.string.biometric_not_recognized)
+                        ? getNotRecognizedString(modality)
                         : getErrorString(modality, error, vendorCode);
                 if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
                 // The camera privacy error can return before the prompt initializes its state,
@@ -1204,8 +1216,11 @@
 
         final PromptInfo promptInfo = (PromptInfo) args.arg1;
         final int[] sensorIds = (int[]) args.arg3;
+
+        // TODO(b/251476085): remove these unused parameters (replaced with SSOT elsewhere)
         final boolean credentialAllowed = (boolean) args.arg4;
         final boolean requireConfirmation = (boolean) args.arg5;
+
         final int userId = args.argi1;
         final String opPackageName = (String) args.arg6;
         final long operationId = args.argl1;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index ddf1457..a5e846a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.biometrics.dagger
 
 import com.android.settingslib.udfps.UdfpsUtils
+import com.android.systemui.biometrics.data.repository.FaceSettingsRepository
+import com.android.systemui.biometrics.data.repository.FaceSettingsRepositoryImpl
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
 import com.android.systemui.biometrics.data.repository.PromptRepository
@@ -47,6 +49,10 @@
 
     @Binds
     @SysUISingleton
+    fun faceSettings(impl: FaceSettingsRepositoryImpl): FaceSettingsRepository
+
+    @Binds
+    @SysUISingleton
     fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt
new file mode 100644
index 0000000..3d5ed82
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import android.os.Handler
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.settings.SecureSettings
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+
+/**
+ * Repository for the global state of users Face Unlock preferences.
+ *
+ * Largely a wrapper around [SecureSettings]'s proxy to Settings.Secure.
+ */
+interface FaceSettingsRepository {
+
+    /** Get Settings for the given user [id]. */
+    fun forUser(id: Int?): FaceUserSettingsRepository
+}
+
+@SysUISingleton
+class FaceSettingsRepositoryImpl
+@Inject
+constructor(
+    @Main private val mainHandler: Handler,
+    private val secureSettings: SecureSettings,
+) : FaceSettingsRepository {
+
+    private val userSettings = ConcurrentHashMap<Int, FaceUserSettingsRepository>()
+
+    override fun forUser(id: Int?): FaceUserSettingsRepository =
+        if (id != null) {
+            userSettings.computeIfAbsent(id) { _ ->
+                FaceUserSettingsRepositoryImpl(id, mainHandler, secureSettings).also { repo ->
+                    repo.start()
+                }
+            }
+        } else {
+            FaceUserSettingsRepositoryImpl.Empty
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
new file mode 100644
index 0000000..68c4a10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.settings.SecureSettings
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flowOf
+
+/** Settings for a user. */
+interface FaceUserSettingsRepository {
+    /** The user's id. */
+    val userId: Int
+
+    /** If BiometricPrompt should always require confirmation (overrides app's preference). */
+    val alwaysRequireConfirmationInApps: Flow<Boolean>
+}
+
+class FaceUserSettingsRepositoryImpl(
+    override val userId: Int,
+    @Main private val mainHandler: Handler,
+    private val secureSettings: SecureSettings,
+) : FaceUserSettingsRepository {
+
+    /** Indefinitely subscribe to user preference changes. */
+    fun start() {
+        watch(
+            FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+            _alwaysRequireConfirmationInApps,
+        )
+    }
+
+    private var _alwaysRequireConfirmationInApps = MutableStateFlow(false)
+    override val alwaysRequireConfirmationInApps: Flow<Boolean> =
+        _alwaysRequireConfirmationInApps.asStateFlow()
+
+    /** Defaults to use when no user is specified. */
+    object Empty : FaceUserSettingsRepository {
+        override val userId = -1
+        override val alwaysRequireConfirmationInApps = flowOf(false)
+    }
+
+    private fun watch(
+        key: String,
+        toUpdate: MutableStateFlow<Boolean>,
+        defaultValue: Boolean = false,
+    ) = secureSettings.watch(userId, mainHandler, key, defaultValue) { v -> toUpdate.value = v }
+}
+
+private fun SecureSettings.watch(
+    userId: Int,
+    handler: Handler,
+    key: String,
+    defaultValue: Boolean = false,
+    onChange: (Boolean) -> Unit,
+) {
+    fun fetch(): Boolean = getIntForUser(key, if (defaultValue) 1 else 0, userId) > 0
+
+    registerContentObserverForUser(
+        key,
+        false /* notifyForDescendants */,
+        object : ContentObserver(handler) {
+            override fun onChange(selfChange: Boolean) = onChange(fetch())
+        },
+        userId
+    )
+
+    onChange(fetch())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b4dc272..b35fbbc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.biometrics.data.repository
 
 import android.hardware.biometrics.PromptInfo
@@ -12,6 +28,10 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
 
 /**
  * A repository for the global state of BiometricPrompt.
@@ -40,7 +60,7 @@
      *
      * Note: overlaps/conflicts with [PromptInfo.isConfirmationRequested], which needs clean up.
      */
-    val isConfirmationRequired: StateFlow<Boolean>
+    val isConfirmationRequired: Flow<Boolean>
 
     /** Update the prompt configuration, which should be set before [isShowing]. */
     fun setPrompt(
@@ -48,7 +68,6 @@
         userId: Int,
         gatekeeperChallenge: Long?,
         kind: PromptKind,
-        requireConfirmation: Boolean = false,
     )
 
     /** Unset the prompt info. */
@@ -56,8 +75,12 @@
 }
 
 @SysUISingleton
-class PromptRepositoryImpl @Inject constructor(private val authController: AuthController) :
-    PromptRepository {
+class PromptRepositoryImpl
+@Inject
+constructor(
+    private val faceSettings: FaceSettingsRepository,
+    private val authController: AuthController,
+) : PromptRepository {
 
     override val isShowing: Flow<Boolean> = conflatedCallbackFlow {
         val callback =
@@ -85,21 +108,30 @@
     private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
     override val kind = _kind.asStateFlow()
 
-    private val _isConfirmationRequired: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
+    private val _faceSettings =
+        _userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged()
+    private val _faceSettingAlwaysRequireConfirmation =
+        _faceSettings.flatMapLatest { it.alwaysRequireConfirmationInApps }.distinctUntilChanged()
+
+    private val _isConfirmationRequired = _promptInfo.map { it?.isConfirmationRequested ?: false }
+    override val isConfirmationRequired =
+        combine(_isConfirmationRequired, _faceSettingAlwaysRequireConfirmation) {
+                appRequiresConfirmation,
+                forceRequireConfirmation ->
+                forceRequireConfirmation || appRequiresConfirmation
+            }
+            .distinctUntilChanged()
 
     override fun setPrompt(
         promptInfo: PromptInfo,
         userId: Int,
         gatekeeperChallenge: Long?,
         kind: PromptKind,
-        requireConfirmation: Boolean,
     ) {
         _kind.value = kind
         _userId.value = userId
         _challenge.value = gatekeeperChallenge
         _promptInfo.value = promptInfo
-        _isConfirmationRequired.value = requireConfirmation
     }
 
     override fun unsetPrompt() {
@@ -107,7 +139,6 @@
         _userId.value = null
         _challenge.value = null
         _kind.value = PromptKind.Biometric()
-        _isConfirmationRequired.value = false
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index e6e07f9..be99dd9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -59,13 +59,15 @@
      */
     val credentialKind: Flow<PromptKind>
 
-    /** If the API caller requested explicit confirmation after successful authentication. */
-    val isConfirmationRequested: Flow<Boolean>
+    /**
+     * If the API caller or the user's personal preferences require explicit confirmation after
+     * successful authentication.
+     */
+    val isConfirmationRequired: Flow<Boolean>
 
     /** Use biometrics for authentication. */
     fun useBiometricsForAuthentication(
         promptInfo: PromptInfo,
-        requireConfirmation: Boolean,
         userId: Int,
         challenge: Long,
         modalities: BiometricModalities,
@@ -114,10 +116,8 @@
             }
         }
 
-    override val isConfirmationRequested: Flow<Boolean> =
-        promptRepository.promptInfo
-            .map { info -> info?.isConfirmationRequested ?: false }
-            .distinctUntilChanged()
+    override val isConfirmationRequired: Flow<Boolean> =
+        promptRepository.isConfirmationRequired.distinctUntilChanged()
 
     override val isCredentialAllowed: Flow<Boolean> =
         promptRepository.promptInfo
@@ -142,7 +142,6 @@
 
     override fun useBiometricsForAuthentication(
         promptInfo: PromptInfo,
-        requireConfirmation: Boolean,
         userId: Int,
         challenge: Long,
         modalities: BiometricModalities
@@ -152,7 +151,6 @@
             userId = userId,
             gatekeeperChallenge = challenge,
             kind = PromptKind.Biometric(modalities),
-            requireConfirmation = requireConfirmation,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 8486c3f..6a7431e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -158,7 +158,7 @@
                 view.updateFingerprintAffordanceSize(iconController)
             }
             if (iconController is HackyCoexIconController) {
-                iconController.faceMode = !viewModel.isConfirmationRequested.first()
+                iconController.faceMode = !viewModel.isConfirmationRequired.first()
             }
 
             // the icon controller must be created before this happens for the legacy
@@ -339,7 +339,13 @@
 
                             launch {
                                 delay(authState.delay)
-                                legacyCallback.onAction(Callback.ACTION_AUTHENTICATED)
+                                legacyCallback.onAction(
+                                    if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+                                        Callback.ACTION_AUTHENTICATED_AND_CONFIRMED
+                                    } else {
+                                        Callback.ACTION_AUTHENTICATED
+                                    }
+                                )
                             }
                         }
                     }
@@ -512,9 +518,10 @@
         }
 
         applicationScope.launch {
-            viewModel.showTemporaryHelp(
+            // help messages from the HAL should be displayed as temporary (i.e. soft) errors
+            viewModel.showTemporaryError(
                 help,
-                messageAfterHelp = modalities.asDefaultHelpMessage(applicationContext),
+                messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1dffa80..1a286cf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -28,6 +28,7 @@
 import android.widget.TextView
 import androidx.core.animation.addListener
 import androidx.core.view.doOnLayout
+import androidx.core.view.isGone
 import androidx.lifecycle.lifecycleScope
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthDialog
@@ -78,9 +79,11 @@
 
         // cache the original position of the icon view (as done in legacy view)
         // this must happen before any size changes can be made
-        var iconHolderOriginalY = 0f
         view.doOnLayout {
-            iconHolderOriginalY = iconHolderView.y
+            // TODO(b/251476085): this old way of positioning has proven itself unreliable
+            // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
+            // pin to the physical sensor
+            val iconHolderOriginalY = iconHolderView.y
 
             // bind to prompt
             // TODO(b/251476085): migrate the legacy panel controller and simplify this
@@ -141,7 +144,11 @@
                                         listOf(
                                             iconHolderView.asVerticalAnimator(
                                                 duration = duration.toLong(),
-                                                toY = iconHolderOriginalY,
+                                                toY =
+                                                    iconHolderOriginalY -
+                                                        viewsToHideWhenSmall
+                                                            .filter { it.isGone }
+                                                            .sumOf { it.height },
                                             ),
                                             viewsToFadeInOnSizeChange.asFadeInAnimator(
                                                 duration = duration.toLong(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
index 9cb91b3..444082c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
@@ -29,10 +29,16 @@
     val needsUserConfirmation: Boolean = false,
     val delay: Long = 0,
 ) {
+    private var wasConfirmed = false
+
     /** If authentication was successful and the user has confirmed (or does not need to). */
     val isAuthenticatedAndConfirmed: Boolean
         get() = isAuthenticated && !needsUserConfirmation
 
+    /** Same as [isAuthenticatedAndConfirmed] but only true if the user clicked a confirm button. */
+    val isAuthenticatedAndExplicitlyConfirmed: Boolean
+        get() = isAuthenticated && wasConfirmed
+
     /** If a successful authentication has not occurred. */
     val isNotAuthenticated: Boolean
         get() = !isAuthenticated
@@ -45,12 +51,16 @@
     val isAuthenticatedByFingerprint: Boolean
         get() = isAuthenticated && authenticatedModality == BiometricModality.Fingerprint
 
-    /** Copies this state, but toggles [needsUserConfirmation] to false. */
-    fun asConfirmed(): PromptAuthState =
+    /**
+     * Copies this state, but toggles [needsUserConfirmation] to false and ensures that
+     * [isAuthenticatedAndExplicitlyConfirmed] is true.
+     */
+    fun asExplicitlyConfirmed(): PromptAuthState =
         PromptAuthState(
-            isAuthenticated = isAuthenticated,
-            authenticatedModality = authenticatedModality,
-            needsUserConfirmation = false,
-            delay = delay,
-        )
+                isAuthenticated = isAuthenticated,
+                authenticatedModality = authenticatedModality,
+                needsUserConfirmation = false,
+                delay = delay,
+            )
+            .apply { wasConfirmed = true }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 2f8ed09..05a5362 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -61,8 +61,11 @@
     /** If the user has successfully authenticated and confirmed (when explicitly required). */
     val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
 
-    /** If the API caller requested explicit confirmation after successful authentication. */
-    val isConfirmationRequested: Flow<Boolean> = interactor.isConfirmationRequested
+    /**
+     * If the API caller or the user's personal preferences require explicit confirmation after
+     * successful authentication.
+     */
+    val isConfirmationRequired: Flow<Boolean> = interactor.isConfirmationRequired
 
     /** The kind of credential the user has. */
     val credentialKind: Flow<PromptKind> = interactor.credentialKind
@@ -91,7 +94,7 @@
                 _forceLargeSize,
                 _forceMediumSize,
                 modalities,
-                interactor.isConfirmationRequested,
+                interactor.isConfirmationRequired,
                 fingerprintStartMode,
             ) { forceLarge, forceMedium, modalities, confirmationRequired, fpStartMode ->
                 when {
@@ -383,7 +386,7 @@
 
     private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
         val availableModalities = modalities.first()
-        val confirmationRequested = interactor.isConfirmationRequested.first()
+        val confirmationRequested = interactor.isConfirmationRequired.first()
 
         if (availableModalities.hasFaceAndFingerprint) {
             // coex only needs confirmation when face is successful, unless it happens on the
@@ -414,7 +417,7 @@
             return
         }
 
-        _isAuthenticated.value = authState.asConfirmed()
+        _isAuthenticated.value = authState.asExplicitlyConfirmed()
         _message.value = PromptMessage.Empty
         _legacyState.value = AuthBiometricView.STATE_AUTHENTICATED
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index a431a59..a90980f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dagger;
 
+import com.android.systemui.globalactions.ShutdownUiModule;
 import com.android.systemui.keyguard.CustomizationProvider;
 import com.android.systemui.statusbar.NotificationInsetsModule;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
@@ -31,6 +32,7 @@
         DependencyProvider.class,
         NotificationInsetsModule.class,
         QsFrameTranslateModule.class,
+        ShutdownUiModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
         SystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 951d077..31f7e88 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -221,7 +221,7 @@
     // TODO(b/267722622): Tracking Bug
     @JvmField
     val WALLPAPER_PICKER_UI_FOR_AIWP =
-            unreleasedFlag(
+            releasedFlag(
                     229,
                     "wallpaper_picker_ui_for_aiwp"
             )
@@ -662,7 +662,7 @@
     @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
     @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection")
     // TODO(b/278622168): Tracking Bug
-    @JvmField val BIOMETRIC_BP_STRONG = unreleasedFlag(2202, "biometric_bp_strong")
+    @JvmField val BIOMETRIC_BP_STRONG = releasedFlag(2202, "biometric_bp_strong")
 
     // 2300 - stylus
     @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used")
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 290bf0d..c5027cc 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -15,27 +15,12 @@
 package com.android.systemui.globalactions;
 
 import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
-import android.annotation.Nullable;
-import android.annotation.StringRes;
-import android.app.Dialog;
 import android.content.Context;
-import android.os.PowerManager;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.ProgressBar;
-import android.widget.TextView;
 
-import com.android.internal.R;
-import com.android.settingslib.Utils;
 import com.android.systemui.plugins.GlobalActions;
-import com.android.systemui.scrim.ScrimDrawable;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -50,12 +35,14 @@
     private final CommandQueue mCommandQueue;
     private final GlobalActionsDialogLite mGlobalActionsDialog;
     private boolean mDisabled;
+    private ShutdownUi mShutdownUi;
 
     @Inject
     public GlobalActionsImpl(Context context, CommandQueue commandQueue,
             GlobalActionsDialogLite globalActionsDialog, BlurUtils blurUtils,
             KeyguardStateController keyguardStateController,
-            DeviceProvisionedController deviceProvisionedController) {
+            DeviceProvisionedController deviceProvisionedController,
+            ShutdownUi shutdownUi) {
         mContext = context;
         mGlobalActionsDialog = globalActionsDialog;
         mKeyguardStateController = keyguardStateController;
@@ -63,6 +50,7 @@
         mCommandQueue = commandQueue;
         mBlurUtils = blurUtils;
         mCommandQueue.addCallback(this);
+        mShutdownUi = shutdownUi;
     }
 
     @Override
@@ -80,103 +68,8 @@
 
     @Override
     public void showShutdownUi(boolean isReboot, String reason) {
-        ScrimDrawable background = new ScrimDrawable();
-
-        final Dialog d = new Dialog(mContext,
-                com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
-
-        d.setOnShowListener(dialog -> {
-            if (mBlurUtils.supportsBlursOnWindows()) {
-                int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
-                background.setAlpha(backgroundAlpha);
-                mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
-                        (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
-            } else {
-                float backgroundAlpha = mContext.getResources().getFloat(
-                        com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
-                background.setAlpha((int) (backgroundAlpha * 255));
-            }
-        });
-
-        // Window initialization
-        Window window = d.getWindow();
-        window.requestFeature(Window.FEATURE_NO_TITLE);
-        window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
-        // Inflate the decor view, so the attributes below are not overwritten by the theme.
-        window.getDecorView();
-        window.getAttributes().width = ViewGroup.LayoutParams.MATCH_PARENT;
-        window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
-        window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
-        window.getAttributes().setFitInsetsTypes(0 /* types */);
-        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-        window.addFlags(
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
-                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
-        window.setBackgroundDrawable(background);
-        window.setWindowAnimations(com.android.systemui.R.style.Animation_ShutdownUi);
-
-        d.setContentView(R.layout.shutdown_dialog);
-        d.setCancelable(false);
-
-        int color;
-        if (mBlurUtils.supportsBlursOnWindows()) {
-            color = Utils.getColorAttrDefaultColor(mContext,
-                    com.android.systemui.R.attr.wallpaperTextColor);
-        } else {
-            color = mContext.getResources().getColor(
-                    com.android.systemui.R.color.global_actions_shutdown_ui_text);
-        }
-
-        ProgressBar bar = d.findViewById(R.id.progress);
-        bar.getIndeterminateDrawable().setTint(color);
-
-        TextView reasonView = d.findViewById(R.id.text1);
-        TextView messageView = d.findViewById(R.id.text2);
-
-        reasonView.setTextColor(color);
-        messageView.setTextColor(color);
-
-        messageView.setText(getRebootMessage(isReboot, reason));
-        String rebootReasonMessage = getReasonMessage(reason);
-        if (rebootReasonMessage != null) {
-            reasonView.setVisibility(View.VISIBLE);
-            reasonView.setText(rebootReasonMessage);
-        }
-
-        d.show();
+        mShutdownUi.showShutdownUi(isReboot, reason);
     }
-
-    @StringRes
-    private int getRebootMessage(boolean isReboot, @Nullable String reason) {
-        if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
-            return R.string.reboot_to_update_reboot;
-        } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
-            return R.string.reboot_to_reset_message;
-        } else if (isReboot) {
-            return R.string.reboot_to_reset_message;
-        } else {
-            return R.string.shutdown_progress;
-        }
-    }
-
-    @Nullable
-    private String getReasonMessage(@Nullable String reason) {
-        if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
-            return mContext.getString(R.string.reboot_to_update_title);
-        } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
-            return mContext.getString(R.string.reboot_to_reset_title);
-        } else {
-            return null;
-        }
-    }
-
     @Override
     public void disable(int displayId, int state1, int state2, boolean animate) {
         final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
new file mode 100644
index 0000000..68dc1b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.globalactions;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.PowerManager;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.R;
+import com.android.settingslib.Utils;
+import com.android.systemui.scrim.ScrimDrawable;
+import com.android.systemui.statusbar.BlurUtils;
+import com.android.systemui.statusbar.phone.ScrimController;
+
+/**
+ * Provides the UI shown during system shutdown.
+ */
+public class ShutdownUi {
+
+    private Context mContext;
+    private BlurUtils mBlurUtils;
+    public ShutdownUi(Context context, BlurUtils blurUtils) {
+        mContext = context;
+        mBlurUtils = blurUtils;
+    }
+
+    /**
+     * Display the shutdown UI.
+     * @param isReboot Whether the device will be rebooting after this shutdown.
+     * @param reason Cause for the shutdown.
+     */
+    public void showShutdownUi(boolean isReboot, String reason) {
+        ScrimDrawable background = new ScrimDrawable();
+
+        final Dialog d = new Dialog(mContext,
+                com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
+
+        d.setOnShowListener(dialog -> {
+            if (mBlurUtils.supportsBlursOnWindows()) {
+                int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
+                background.setAlpha(backgroundAlpha);
+                mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
+                        (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
+            } else {
+                float backgroundAlpha = mContext.getResources().getFloat(
+                        com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
+                background.setAlpha((int) (backgroundAlpha * 255));
+            }
+        });
+
+        // Window initialization
+        Window window = d.getWindow();
+        window.requestFeature(Window.FEATURE_NO_TITLE);
+        window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+        // Inflate the decor view, so the attributes below are not overwritten by the theme.
+        window.getDecorView();
+        window.getAttributes().width = ViewGroup.LayoutParams.MATCH_PARENT;
+        window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
+        window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+        window.getAttributes().setFitInsetsTypes(0 /* types */);
+        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+        window.addFlags(
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+        window.setBackgroundDrawable(background);
+        window.setWindowAnimations(com.android.systemui.R.style.Animation_ShutdownUi);
+
+        d.setContentView(getShutdownDialogContent(isReboot));
+        d.setCancelable(false);
+
+        int color;
+        if (mBlurUtils.supportsBlursOnWindows()) {
+            color = Utils.getColorAttrDefaultColor(mContext,
+                    com.android.systemui.R.attr.wallpaperTextColor);
+        } else {
+            color = mContext.getResources().getColor(
+                    com.android.systemui.R.color.global_actions_shutdown_ui_text);
+        }
+
+        ProgressBar bar = d.findViewById(R.id.progress);
+        bar.getIndeterminateDrawable().setTint(color);
+
+        TextView reasonView = d.findViewById(R.id.text1);
+        TextView messageView = d.findViewById(R.id.text2);
+
+        reasonView.setTextColor(color);
+        messageView.setTextColor(color);
+
+        messageView.setText(getRebootMessage(isReboot, reason));
+        String rebootReasonMessage = getReasonMessage(reason);
+        if (rebootReasonMessage != null) {
+            reasonView.setVisibility(View.VISIBLE);
+            reasonView.setText(rebootReasonMessage);
+        }
+
+        d.show();
+    }
+
+    /**
+     * Returns the layout resource to use for UI while shutting down.
+     * @param isReboot Whether this is a reboot or a shutdown.
+     * @return
+     */
+    public int getShutdownDialogContent(boolean isReboot) {
+        return R.layout.shutdown_dialog;
+    }
+
+    @StringRes
+    @VisibleForTesting int getRebootMessage(boolean isReboot, @Nullable String reason) {
+        if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
+            return R.string.reboot_to_update_reboot;
+        } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
+            return R.string.reboot_to_reset_message;
+        } else if (isReboot) {
+            return R.string.reboot_to_reset_message;
+        } else {
+            return R.string.shutdown_progress;
+        }
+    }
+
+    @Nullable
+    @VisibleForTesting String getReasonMessage(@Nullable String reason) {
+        if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
+            return mContext.getString(R.string.reboot_to_update_title);
+        } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
+            return mContext.getString(R.string.reboot_to_reset_title);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt
new file mode 100644
index 0000000..b7285da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.globalactions
+
+import android.content.Context
+import com.android.systemui.statusbar.BlurUtils
+import dagger.Module
+import dagger.Provides
+
+/** Provides the UI shown during system shutdown. */
+@Module
+class ShutdownUiModule {
+    /** Shutdown UI provider. */
+    @Provides
+    fun provideShutdownUi(context: Context?, blurUtils: BlurUtils?): ShutdownUi {
+        return ShutdownUi(context, blurUtils)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index 640adcc..5e489b0 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -20,15 +20,14 @@
 import com.android.systemui.dagger.DependencyProvider;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.SystemUIBinder;
 import com.android.systemui.dagger.SystemUIModule;
+import com.android.systemui.globalactions.ShutdownUiModule;
+import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.recents.RecentsModule;
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.row.NotificationRowModule;
 
-import com.android.systemui.keyguard.dagger.KeyguardModule;
-import com.android.systemui.recents.RecentsModule;
-
 import dagger.Subcomponent;
 
 /**
@@ -43,6 +42,7 @@
         NotificationRowModule.class,
         NotificationsModule.class,
         RecentsModule.class,
+        ShutdownUiModule.class,
         SystemUIModule.class,
         TvSystemUIBinder.class,
         TVSystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
new file mode 100644
index 0000000..0df4fbf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.captureMany
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+private const val USER_ID = 8
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class FaceSettingsRepositoryImplTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+    private val testScope = TestScope()
+
+    @Mock private lateinit var mainHandler: Handler
+    @Mock private lateinit var secureSettings: SecureSettings
+
+    private lateinit var repository: FaceSettingsRepositoryImpl
+
+    @Before
+    fun setup() {
+        repository = FaceSettingsRepositoryImpl(mainHandler, secureSettings)
+    }
+
+    @Test
+    fun createsOneRepositoryPerUser() =
+        testScope.runTest {
+            val userRepo = repository.forUser(USER_ID)
+
+            assertThat(userRepo.userId).isEqualTo(USER_ID)
+
+            assertThat(repository.forUser(USER_ID)).isSameInstanceAs(userRepo)
+            assertThat(repository.forUser(USER_ID + 1)).isNotSameInstanceAs(userRepo)
+        }
+
+    @Test
+    fun startsRepoImmediatelyWithAllSettingKeys() =
+        testScope.runTest {
+            val userRepo = repository.forUser(USER_ID)
+
+            val keys =
+                captureMany<String> {
+                    verify(secureSettings)
+                        .registerContentObserverForUser(capture(), anyBoolean(), any(), eq(USER_ID))
+                }
+
+            assertThat(keys).containsExactly(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION)
+        }
+
+    @Test
+    fun forwardsSettingsValues() = runTest {
+        val userRepo = repository.forUser(USER_ID)
+
+        val intAsBooleanSettings =
+            listOf(
+                FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION to
+                    collectLastValue(userRepo.alwaysRequireConfirmationInApps)
+            )
+
+        for ((setting, accessor) in intAsBooleanSettings) {
+            val observer =
+                withArgCaptor<ContentObserver> {
+                    verify(secureSettings)
+                        .registerContentObserverForUser(
+                            eq(setting),
+                            anyBoolean(),
+                            capture(),
+                            eq(USER_ID)
+                        )
+                }
+
+            for (value in listOf(true, false)) {
+                secureSettings.mockIntSetting(setting, if (value) 1 else 0)
+                observer.onChange(false)
+                assertThat(accessor()).isEqualTo(value)
+            }
+        }
+    }
+
+    private fun SecureSettings.mockIntSetting(key: String, value: Int) {
+        whenever(getIntForUser(eq(key), anyInt(), eq(USER_ID))).thenReturn(value)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index 4836af6..ec7ce63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.biometrics.data.repository
 
 import android.hardware.biometrics.PromptInfo
@@ -5,12 +21,16 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -21,61 +41,109 @@
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 
+private const val USER_ID = 9
+private const val CHALLENGE = 90L
+
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class PromptRepositoryImplTest : SysuiTestCase() {
 
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
 
+    private val testScope = TestScope()
+    private val faceSettings = FakeFaceSettingsRepository()
+
     @Mock private lateinit var authController: AuthController
 
     private lateinit var repository: PromptRepositoryImpl
 
     @Before
     fun setup() {
-        repository = PromptRepositoryImpl(authController)
+        repository = PromptRepositoryImpl(faceSettings, authController)
     }
 
     @Test
-    fun isShowing() = runBlockingTest {
-        whenever(authController.isShowing).thenReturn(true)
+    fun isShowing() =
+        testScope.runTest {
+            whenever(authController.isShowing).thenReturn(true)
 
-        val values = mutableListOf<Boolean>()
-        val job = launch { repository.isShowing.toList(values) }
-        assertThat(values).containsExactly(true)
+            val values = mutableListOf<Boolean>()
+            val job = launch { repository.isShowing.toList(values) }
+            runCurrent()
 
-        withArgCaptor<AuthController.Callback> {
-            verify(authController).addCallback(capture())
+            assertThat(values).containsExactly(true)
 
-            value.onBiometricPromptShown()
-            assertThat(values).containsExactly(true, true)
+            withArgCaptor<AuthController.Callback> {
+                verify(authController).addCallback(capture())
 
-            value.onBiometricPromptDismissed()
-            assertThat(values).containsExactly(true, true, false).inOrder()
+                value.onBiometricPromptShown()
+                runCurrent()
+                assertThat(values).containsExactly(true, true)
 
-            job.cancel()
-            verify(authController).removeCallback(eq(value))
+                value.onBiometricPromptDismissed()
+                runCurrent()
+                assertThat(values).containsExactly(true, true, false).inOrder()
+
+                job.cancel()
+                runCurrent()
+                verify(authController).removeCallback(eq(value))
+            }
         }
-    }
 
     @Test
-    fun setsAndUnsetsPrompt() = runBlockingTest {
-        val kind = PromptKind.Pin
-        val uid = 8
-        val challenge = 90L
-        val promptInfo = PromptInfo()
+    fun isConfirmationRequired_whenNotForced() =
+        testScope.runTest {
+            faceSettings.setUserSettings(USER_ID, alwaysRequireConfirmationInApps = false)
+            val isConfirmationRequired by collectLastValue(repository.isConfirmationRequired)
 
-        repository.setPrompt(promptInfo, uid, challenge, kind)
+            for (case in listOf(true, false)) {
+                repository.setPrompt(
+                    PromptInfo().apply { isConfirmationRequested = case },
+                    USER_ID,
+                    CHALLENGE,
+                    PromptKind.Biometric()
+                )
 
-        assertThat(repository.kind.value).isEqualTo(kind)
-        assertThat(repository.userId.value).isEqualTo(uid)
-        assertThat(repository.challenge.value).isEqualTo(challenge)
-        assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+                assertThat(isConfirmationRequired).isEqualTo(case)
+            }
+        }
 
-        repository.unsetPrompt()
+    @Test
+    fun isConfirmationRequired_whenForced() =
+        testScope.runTest {
+            faceSettings.setUserSettings(USER_ID, alwaysRequireConfirmationInApps = true)
+            val isConfirmationRequired by collectLastValue(repository.isConfirmationRequired)
 
-        assertThat(repository.promptInfo.value).isNull()
-        assertThat(repository.userId.value).isNull()
-        assertThat(repository.challenge.value).isNull()
-    }
+            for (case in listOf(true, false)) {
+                repository.setPrompt(
+                    PromptInfo().apply { isConfirmationRequested = case },
+                    USER_ID,
+                    CHALLENGE,
+                    PromptKind.Biometric()
+                )
+
+                assertThat(isConfirmationRequired).isTrue()
+            }
+        }
+
+    @Test
+    fun setsAndUnsetsPrompt() =
+        testScope.runTest {
+            val kind = PromptKind.Pin
+            val promptInfo = PromptInfo()
+
+            repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind)
+
+            assertThat(repository.kind.value).isEqualTo(kind)
+            assertThat(repository.userId.value).isEqualTo(USER_ID)
+            assertThat(repository.challenge.value).isEqualTo(CHALLENGE)
+            assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+
+            repository.unsetPrompt()
+
+            assertThat(repository.promptInfo.value).isNull()
+            assertThat(repository.userId.value).isNull()
+            assertThat(repository.challenge.value).isNull()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index a62ea3b..81cbaea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -106,17 +106,11 @@
         val currentPrompt by collectLastValue(interactor.prompt)
         val credentialKind by collectLastValue(interactor.credentialKind)
         val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed)
-        val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequested)
+        val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
 
         assertThat(currentPrompt).isNull()
 
-        interactor.useBiometricsForAuthentication(
-            info,
-            confirmationRequired,
-            USER_ID,
-            CHALLENGE,
-            modalities
-        )
+        interactor.useBiometricsForAuthentication(info, USER_ID, CHALLENGE, modalities)
 
         assertThat(currentPrompt).isNotNull()
         assertThat(currentPrompt?.title).isEqualTo(TITLE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
index 689bb00..fff1b81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
@@ -33,6 +33,7 @@
         with(PromptAuthState(isAuthenticated = false)) {
             assertThat(isNotAuthenticated).isTrue()
             assertThat(isAuthenticatedAndConfirmed).isFalse()
+            assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
             assertThat(isAuthenticatedByFace).isFalse()
             assertThat(isAuthenticatedByFingerprint).isFalse()
         }
@@ -43,6 +44,7 @@
         with(PromptAuthState(isAuthenticated = true)) {
             assertThat(isNotAuthenticated).isFalse()
             assertThat(isAuthenticatedAndConfirmed).isTrue()
+            assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
             assertThat(isAuthenticatedByFace).isFalse()
             assertThat(isAuthenticatedByFingerprint).isFalse()
         }
@@ -50,10 +52,12 @@
         with(PromptAuthState(isAuthenticated = true, needsUserConfirmation = true)) {
             assertThat(isNotAuthenticated).isFalse()
             assertThat(isAuthenticatedAndConfirmed).isFalse()
+            assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
             assertThat(isAuthenticatedByFace).isFalse()
             assertThat(isAuthenticatedByFingerprint).isFalse()
 
-            assertThat(asConfirmed().isAuthenticatedAndConfirmed).isTrue()
+            assertThat(asExplicitlyConfirmed().isAuthenticatedAndConfirmed).isTrue()
+            assertThat(asExplicitlyConfirmed().isAuthenticatedAndExplicitlyConfirmed).isTrue()
         }
     }
 
@@ -64,6 +68,7 @@
         ) {
             assertThat(isNotAuthenticated).isFalse()
             assertThat(isAuthenticatedAndConfirmed).isTrue()
+            assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
             assertThat(isAuthenticatedByFace).isTrue()
             assertThat(isAuthenticatedByFingerprint).isFalse()
         }
@@ -79,6 +84,7 @@
         ) {
             assertThat(isNotAuthenticated).isFalse()
             assertThat(isAuthenticatedAndConfirmed).isTrue()
+            assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
             assertThat(isAuthenticatedByFace).isFalse()
             assertThat(isAuthenticatedByFingerprint).isTrue()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 3ba6004..5b3edab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -631,7 +631,6 @@
         }
     useBiometricsForAuthentication(
         info,
-        requireConfirmation,
         USER_ID,
         CHALLENGE,
         BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
new file mode 100644
index 0000000..9d9b263
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.globalactions;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+
+import android.os.PowerManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.BlurUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ShutdownUiTest extends SysuiTestCase {
+
+    ShutdownUi mShutdownUi;
+    @Mock
+    BlurUtils mBlurUtils;
+
+    @Before
+    public void setUp() throws Exception {
+        mShutdownUi = new ShutdownUi(getContext(), mBlurUtils);
+    }
+
+    @Test
+    public void getRebootMessage_update() {
+        int messageId = mShutdownUi.getRebootMessage(true, PowerManager.REBOOT_RECOVERY_UPDATE);
+        assertEquals(messageId, R.string.reboot_to_update_reboot);
+    }
+
+    @Test
+    public void getRebootMessage_rebootDefault() {
+        int messageId = mShutdownUi.getRebootMessage(true, "anything-else");
+        assertEquals(messageId, R.string.reboot_to_reset_message);
+    }
+
+    @Test
+    public void getRebootMessage_shutdown() {
+        int messageId = mShutdownUi.getRebootMessage(false, "anything-else");
+        assertEquals(messageId, R.string.shutdown_progress);
+    }
+
+    @Test
+    public void getReasonMessage_update() {
+        String message = mShutdownUi.getReasonMessage(PowerManager.REBOOT_RECOVERY_UPDATE);
+        assertEquals(message, mContext.getString(R.string.reboot_to_update_title));
+    }
+
+    @Test
+    public void getReasonMessage_rebootDefault() {
+        String message = mShutdownUi.getReasonMessage(PowerManager.REBOOT_RECOVERY);
+        assertEquals(message, mContext.getString(R.string.reboot_to_reset_title));
+    }
+
+    @Test
+    public void getRebootMessage_defaultToNone() {
+        String message = mShutdownUi.getReasonMessage("anything-else");
+        assertNull(message);
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt
new file mode 100644
index 0000000..af2706e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import kotlinx.coroutines.flow.flowOf
+
+/** Fake settings for tests. */
+class FakeFaceSettingsRepository : FaceSettingsRepository {
+
+    private val userRepositories = mutableMapOf<Int, FaceUserSettingsRepository>()
+
+    /** Add fixed settings for a user. */
+    fun setUserSettings(userId: Int, alwaysRequireConfirmationInApps: Boolean = false) {
+        userRepositories[userId] =
+            object : FaceUserSettingsRepository {
+                override val userId = userId
+                override val alwaysRequireConfirmationInApps =
+                    flowOf(alwaysRequireConfirmationInApps)
+            }
+    }
+
+    override fun forUser(id: Int?) = userRepositories[id] ?: FaceUserSettingsRepositoryImpl.Empty
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index d270700..42ec8fed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -31,13 +31,20 @@
         userId: Int,
         gatekeeperChallenge: Long?,
         kind: PromptKind,
-        requireConfirmation: Boolean,
+    ) = setPrompt(promptInfo, userId, gatekeeperChallenge, kind, forceConfirmation = false)
+
+    fun setPrompt(
+        promptInfo: PromptInfo,
+        userId: Int,
+        gatekeeperChallenge: Long?,
+        kind: PromptKind,
+        forceConfirmation: Boolean = false,
     ) {
         _promptInfo.value = promptInfo
         _userId.value = userId
         _challenge.value = gatekeeperChallenge
         _kind.value = kind
-        _isConfirmationRequired.value = requireConfirmation
+        _isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation
     }
 
     override fun unsetPrompt() {
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 8ef2a1b..cb5e7f1 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -21,7 +21,10 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
 
+import static com.android.server.biometrics.BiometricSensor.STATE_CANCELING;
+import static com.android.server.biometrics.BiometricSensor.STATE_UNKNOWN;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -439,6 +442,13 @@
             return false;
         }
 
+        final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+                || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+        if (errorLockout) {
+            cancelAllSensors(sensor -> Utils.isAtLeastStrength(sensorIdToStrength(sensorId),
+                    sensor.getCurrentStrength()));
+        }
+
         mErrorEscrow = error;
         mVendorCodeEscrow = vendorCode;
 
@@ -477,8 +487,6 @@
 
             case STATE_AUTH_STARTED:
             case STATE_AUTH_STARTED_UI_SHOWING: {
-                final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
-                        || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
                 if (isAllowDeviceCredential() && errorLockout) {
                     // SystemUI handles transition from biometric to device credential.
                     mState = STATE_SHOWING_DEVICE_CREDENTIAL;
@@ -675,7 +683,9 @@
     }
 
     private boolean pauseSensorIfSupported(int sensorId) {
-        if (sensorIdToModality(sensorId) == TYPE_FACE) {
+        boolean isSensorCancelling = sensorIdToState(sensorId) == STATE_CANCELING;
+        // If the sensor is locked out, canceling sensors operation is handled in onErrorReceived()
+        if (sensorIdToModality(sensorId) == TYPE_FACE && !isSensorCancelling) {
             cancelAllSensors(sensor -> sensor.id == sensorId);
             return true;
         }
@@ -948,6 +958,27 @@
         return TYPE_NONE;
     }
 
+    private @BiometricSensor.SensorState int sensorIdToState(int sensorId) {
+        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+            if (sensorId == sensor.id) {
+                return sensor.getSensorState();
+            }
+        }
+        Slog.e(TAG, "Unknown sensor: " + sensorId);
+        return STATE_UNKNOWN;
+    }
+
+    @BiometricManager.Authenticators.Types
+    private int sensorIdToStrength(int sensorId) {
+        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+            if (sensorId == sensor.id) {
+                return sensor.getCurrentStrength();
+            }
+        }
+        Slog.e(TAG, "Unknown sensor: " + sensorId);
+        return BIOMETRIC_CONVENIENCE;
+    }
+
     private String getAcquiredMessageForSensor(int sensorId, int acquiredInfo, int vendorCode) {
         final @Modality int modality = sensorIdToModality(sensorId);
         switch (modality) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
new file mode 100644
index 0000000..6727fbc
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+/**
+ * Interface for biometric operations to get camera privacy state.
+ */
+public interface BiometricSensorPrivacy {
+    /* Returns true if privacy is enabled and camera access is disabled. */
+    boolean isCameraPrivacyEnabled();
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java
new file mode 100644
index 0000000..b6701da
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import android.annotation.Nullable;
+import android.hardware.SensorPrivacyManager;
+
+public class BiometricSensorPrivacyImpl implements
+        BiometricSensorPrivacy {
+    private final SensorPrivacyManager mSensorPrivacyManager;
+
+    public BiometricSensorPrivacyImpl(@Nullable SensorPrivacyManager sensorPrivacyManager) {
+        mSensorPrivacyManager = sensorPrivacyManager;
+    }
+
+    @Override
+    public boolean isCameraPrivacyEnabled() {
+        return mSensorPrivacyManager != null && mSensorPrivacyManager
+                .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 0942d85..1fa97a3 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -33,6 +33,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
@@ -124,6 +125,8 @@
     AuthSession mAuthSession;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
+    private final BiometricSensorPrivacy mBiometricSensorPrivacy;
+
     /**
      * Tracks authenticatorId invalidation. For more details, see
      * {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}.
@@ -933,7 +936,7 @@
 
         return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
                 userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
-                getContext());
+                getContext(), mBiometricSensorPrivacy);
     }
 
     /**
@@ -1026,6 +1029,11 @@
         public UserManager getUserManager(Context context) {
             return context.getSystemService(UserManager.class);
         }
+
+        public BiometricSensorPrivacy getBiometricSensorPrivacy(Context context) {
+            return new BiometricSensorPrivacyImpl(context.getSystemService(
+                    SensorPrivacyManager.class));
+        }
     }
 
     /**
@@ -1054,6 +1062,7 @@
         mRequestCounter = mInjector.getRequestGenerator();
         mBiometricContext = injector.getBiometricContext(context);
         mUserManager = injector.getUserManager(context);
+        mBiometricSensorPrivacy = injector.getBiometricSensorPrivacy(context);
 
         try {
             injector.getActivityManagerService().registerUserSwitchObserver(
@@ -1290,7 +1299,7 @@
                 final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
                         mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
                         opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
-                        getContext());
+                        getContext(), mBiometricSensorPrivacy);
 
                 final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
 
@@ -1300,9 +1309,7 @@
                         + promptInfo.isIgnoreEnrollmentState());
                 // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can
                 // be shown for this case.
-                if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS
-                        || preAuthStatus.second
-                        == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) {
+                if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
                     // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
                     // CREDENTIAL is requested and available, set the bundle to only request
                     // CREDENTIAL.
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 3813fd1..e6f25cb 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -27,7 +27,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustManager;
 import android.content.Context;
-import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.PromptInfo;
@@ -73,13 +72,16 @@
     final Context context;
     private final boolean mBiometricRequested;
     private final int mBiometricStrengthRequested;
+    private final BiometricSensorPrivacy mBiometricSensorPrivacy;
+
     private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
             boolean credentialRequested, List<BiometricSensor> eligibleSensors,
             List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
             boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
-            Context context) {
+            Context context, BiometricSensorPrivacy biometricSensorPrivacy) {
         mBiometricRequested = biometricRequested;
         mBiometricStrengthRequested = biometricStrengthRequested;
+        mBiometricSensorPrivacy = biometricSensorPrivacy;
         this.credentialRequested = credentialRequested;
 
         this.eligibleSensors = eligibleSensors;
@@ -96,7 +98,8 @@
             BiometricService.SettingObserver settingObserver,
             List<BiometricSensor> sensors,
             int userId, PromptInfo promptInfo, String opPackageName,
-            boolean checkDevicePolicyManager, Context context)
+            boolean checkDevicePolicyManager, Context context,
+            BiometricSensorPrivacy biometricSensorPrivacy)
             throws RemoteException {
 
         final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -124,7 +127,7 @@
                         checkDevicePolicyManager, requestedStrength,
                         promptInfo.getAllowedSensorIds(),
                         promptInfo.isIgnoreEnrollmentState(),
-                        context);
+                        biometricSensorPrivacy);
 
                 Slog.d(TAG, "Package: " + opPackageName
                         + " Sensor ID: " + sensor.id
@@ -138,7 +141,7 @@
                 //
                 // Note: if only a certain sensor is required and the privacy is enabled,
                 // canAuthenticate() will return false.
-                if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) {
+                if (status == AUTHENTICATOR_OK) {
                     eligibleSensors.add(sensor);
                 } else {
                     ineligibleSensors.add(new Pair<>(sensor, status));
@@ -148,7 +151,7 @@
 
         return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
                 eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
-                promptInfo.isIgnoreEnrollmentState(), userId, context);
+                promptInfo.isIgnoreEnrollmentState(), userId, context, biometricSensorPrivacy);
     }
 
     /**
@@ -165,7 +168,7 @@
             BiometricSensor sensor, int userId, String opPackageName,
             boolean checkDevicePolicyManager, int requestedStrength,
             @NonNull List<Integer> requestedSensorIds,
-            boolean ignoreEnrollmentState, Context context) {
+            boolean ignoreEnrollmentState, BiometricSensorPrivacy biometricSensorPrivacy) {
 
         if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
             return BIOMETRIC_NO_HARDWARE;
@@ -191,12 +194,10 @@
                     && !ignoreEnrollmentState) {
                 return BIOMETRIC_NOT_ENROLLED;
             }
-            final SensorPrivacyManager sensorPrivacyManager = context
-                    .getSystemService(SensorPrivacyManager.class);
 
-            if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) {
-                if (sensorPrivacyManager
-                        .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) {
+            if (biometricSensorPrivacy != null && sensor.modality == TYPE_FACE) {
+                if (biometricSensorPrivacy.isCameraPrivacyEnabled()) {
+                    //Camera privacy is enabled as the access is disabled
                     return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
                 }
             }
@@ -266,17 +267,30 @@
     }
 
     private Pair<BiometricSensor, Integer> calculateErrorByPriority() {
-        // If the caller requested STRONG, and the device contains both STRONG and non-STRONG
-        // sensors, prioritize BIOMETRIC_NOT_ENROLLED over the weak sensor's
-        // BIOMETRIC_INSUFFICIENT_STRENGTH error. Pretty sure we can always prioritize
-        // BIOMETRIC_NOT_ENROLLED over any other error (unless of course its calculation is
-        // wrong, in which case we should fix that instead).
+        Pair<BiometricSensor, Integer> sensorNotEnrolled = null;
+        Pair<BiometricSensor, Integer> sensorLockout = null;
         for (Pair<BiometricSensor, Integer> pair : ineligibleSensors) {
+            int status = pair.second;
+            if (status == BIOMETRIC_LOCKOUT_TIMED || status == BIOMETRIC_LOCKOUT_PERMANENT) {
+                sensorLockout = pair;
+            }
             if (pair.second == BIOMETRIC_NOT_ENROLLED) {
-                return pair;
+                sensorNotEnrolled = pair;
             }
         }
 
+        // If there is a sensor locked out, prioritize lockout over other sensor's error.
+        // See b/286923477.
+        if (sensorLockout != null) {
+            return sensorLockout;
+        }
+
+        // If the caller requested STRONG, and the device contains both STRONG and non-STRONG
+        // sensors, prioritize BIOMETRIC_NOT_ENROLLED over the weak sensor's
+        // BIOMETRIC_INSUFFICIENT_STRENGTH error.
+        if (sensorNotEnrolled != null) {
+            return sensorNotEnrolled;
+        }
         return ineligibleSensors.get(0);
     }
 
@@ -292,13 +306,9 @@
         @AuthenticatorStatus final int status;
         @BiometricAuthenticator.Modality int modality = TYPE_NONE;
 
-        final SensorPrivacyManager sensorPrivacyManager = context
-                .getSystemService(SensorPrivacyManager.class);
-
         boolean cameraPrivacyEnabled = false;
-        if (sensorPrivacyManager != null) {
-            cameraPrivacyEnabled = sensorPrivacyManager
-                    .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId);
+        if (mBiometricSensorPrivacy != null) {
+            cameraPrivacyEnabled = mBiometricSensorPrivacy.isCameraPrivacyEnabled();
         }
 
         if (mBiometricRequested && credentialRequested) {
@@ -315,7 +325,7 @@
                     // and the face sensor privacy is enabled then return
                     // BIOMETRIC_SENSOR_PRIVACY_ENABLED.
                     //
-                    // Note: This sensor will still be eligible for calls to authenticate.
+                    // Note: This sensor will not be eligible for calls to authenticate.
                     status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
                 } else {
                     status = AUTHENTICATOR_OK;
@@ -340,7 +350,7 @@
                     // If the only modality requested is face and the privacy is enabled
                     // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED.
                     //
-                    // Note: This sensor will still be eligible for calls to authenticate.
+                    // Note: This sensor will not be eligible for calls to authenticate.
                     status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
                 } else {
                     status = AUTHENTICATOR_OK;
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 46c77e8..aa6a0f1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -210,4 +210,8 @@
     public boolean isInterruptable() {
         return true;
     }
+
+    public boolean isAlreadyCancelled() {
+        return mAlreadyCancelled;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 05ca6e4..6ac1631 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -266,8 +266,12 @@
             }
         } else {
             if (isBackgroundAuth) {
-                Slog.e(TAG, "cancelling due to background auth");
-                cancel();
+                Slog.e(TAG, "Sending cancel to client(Due to background auth)");
+                if (mTaskStackListener != null) {
+                    mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+                }
+                sendCancelOnly(getListener());
+                mCallback.onClientFinished(this, false);
             } else {
                 // Allow system-defined limit of number of attempts before giving up
                 if (mShouldUseLockoutTracker) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 7fa4d6c..78c3808 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -268,6 +268,14 @@
             return;
         }
 
+        if (mCurrentOperation.isAcquisitionOperation()) {
+            AcquisitionClient client = (AcquisitionClient) mCurrentOperation.getClientMonitor();
+            if (client.isAlreadyCancelled()) {
+                mCurrentOperation.cancel(mHandler, mInternalCallback);
+                return;
+            }
+        }
+
         if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) {
             mGestureAvailabilityDispatcher.markSensorActive(
                     mCurrentOperation.getSensorId(), true /* active */);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index c7c0fab..7701bc6 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -701,11 +701,15 @@
                         maxDisplayMode == null ? mInfo.width : maxDisplayMode.getPhysicalWidth();
                 final int maxHeight =
                         maxDisplayMode == null ? mInfo.height : maxDisplayMode.getPhysicalHeight();
-                mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
-                        mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
 
-                mInfo.roundedCorners = RoundedCorners.fromResources(
-                        res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+                // We cannot determine cutouts and rounded corners of external displays.
+                if (mStaticDisplayInfo.isInternal) {
+                    mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
+                            mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+                    mInfo.roundedCorners = RoundedCorners.fromResources(
+                            res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+                }
+
                 mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
 
                 mInfo.displayShape = DisplayShape.fromResources(
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index b4613a7..efb3622 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -171,7 +171,7 @@
             true,  /* disableAod */
             true,  /* disableLaunchBoost */
             true,  /* disableOptionalSensors */
-            true,  /* disableVibration */
+            false,  /* disableVibration */
             false, /* enableAdjustBrightness */
             false, /* enableDataSaver */
             true,  /* enableFirewall */
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b7dbaf9..f89f73c9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -37,6 +37,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -1009,6 +1010,72 @@
                 0.001f);
     }
 
+    @Test
+    public void test_getDisplayDeviceInfoLocked_internalDisplay_usesCutoutAndCorners()
+            throws Exception {
+        setupCutoutAndRoundedCorners();
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.info.isInternal = true;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn on / initialize
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+                0);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        mListener.changedDisplays.clear();
+
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(info.displayCutout).isNotNull();
+        assertThat(info.displayCutout.getBoundingRectTop()).isEqualTo(new Rect(507, 33, 573, 99));
+        assertThat(info.roundedCorners).isNotNull();
+        assertThat(info.roundedCorners.getRoundedCorner(0).getRadius()).isEqualTo(5);
+    }
+
+    @Test public void test_getDisplayDeviceInfoLocked_externalDisplay_doesNotUseCutoutOrCorners()
+            throws Exception {
+        setupCutoutAndRoundedCorners();
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.info.isInternal = false;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn on / initialize
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+                0);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        mListener.changedDisplays.clear();
+
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(info.displayCutout).isNull();
+        assertThat(info.roundedCorners).isNull();
+    }
+
+    private void setupCutoutAndRoundedCorners() {
+        String sampleCutout = "M 507,66\n"
+                + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
+                + "Z\n"
+                + "@left\n";
+        // Setup some default cutout
+        when(mMockedResources.getString(
+                com.android.internal.R.string.config_mainBuiltInDisplayCutout))
+                .thenReturn(sampleCutout);
+        when(mMockedResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5);
+    }
+
     private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
                                   float expectedXdpi,
                                   float expectedYDpi,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 662477d..8346050 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -26,6 +26,7 @@
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING;
+import static com.android.server.biometrics.BiometricServiceStateProto.STATE_ERROR_PENDING_SYSUI;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -48,6 +49,7 @@
 import android.app.trust.ITrustManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ComponentInfoInternal;
@@ -104,6 +106,7 @@
     @Mock private KeyStore mKeyStore;
     @Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
     @Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
+    @Mock BiometricSensorPrivacy mBiometricSensorPrivacy;
 
     private Random mRandom;
     private IBinder mToken;
@@ -210,6 +213,40 @@
     }
 
     @Test
+    public void testOnErrorReceived_lockoutError() throws RemoteException {
+        setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+        setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+                mock(IBiometricAuthenticator.class));
+        final AuthSession session = createAuthSession(mSensors,
+                false /* checkDevicePolicyManager */,
+                Authenticators.BIOMETRIC_STRONG,
+                TEST_REQUEST_ID,
+                0 /* operationId */,
+                0 /* userId */);
+        session.goToInitialState();
+        for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+            assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
+            session.onCookieReceived(
+                    session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie());
+        }
+        assertTrue(session.allCookiesReceived());
+        assertEquals(STATE_AUTH_STARTED, session.getState());
+
+        // Either of strong sensor's lockout should cancel both sensors.
+        final int cookie1 = session.mPreAuthInfo.eligibleSensors.get(0).getCookie();
+        session.onErrorReceived(0, cookie1, BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, 0);
+        for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+            assertEquals(BiometricSensor.STATE_CANCELING, sensor.getSensorState());
+        }
+        assertEquals(STATE_ERROR_PENDING_SYSUI, session.getState());
+
+        // If the sensor is STATE_CANCELING, delayed onAuthenticationRejected() shouldn't change the
+        // session state to STATE_AUTH_PAUSED.
+        session.onAuthenticationRejected(1);
+        assertEquals(STATE_ERROR_PENDING_SYSUI, session.getState());
+    }
+
+    @Test
     public void testCancelReducesAppetiteForCookies() throws Exception {
         setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
                 mock(IBiometricAuthenticator.class));
@@ -571,7 +608,8 @@
                 promptInfo,
                 TEST_PACKAGE,
                 checkDevicePolicyManager,
-                mContext);
+                mContext,
+                mBiometricSensorPrivacy);
     }
 
     private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 67be376..41f7dbc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -18,7 +18,6 @@
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
@@ -183,6 +182,10 @@
                 .thenReturn(ERROR_HW_UNAVAILABLE);
         when(mResources.getString(R.string.biometric_not_recognized))
                 .thenReturn(ERROR_NOT_RECOGNIZED);
+        when(mResources.getString(R.string.biometric_face_not_recognized))
+                .thenReturn(ERROR_NOT_RECOGNIZED);
+        when(mResources.getString(R.string.fingerprint_error_not_match))
+                .thenReturn(ERROR_NOT_RECOGNIZED);
         when(mResources.getString(R.string.biometric_error_user_canceled))
                 .thenReturn(ERROR_USER_CANCELED);
 
@@ -903,6 +906,45 @@
     }
 
     @Test
+    public void testMultiBiometricAuth_whenLockoutTimed_sendsErrorAndModality()
+            throws Exception {
+        testMultiBiometricAuth_whenLockout(LockoutTracker.LOCKOUT_TIMED,
+                BiometricPrompt.BIOMETRIC_ERROR_LOCKOUT);
+    }
+
+    @Test
+    public void testMultiBiometricAuth_whenLockoutPermanent_sendsErrorAndModality()
+            throws Exception {
+        testMultiBiometricAuth_whenLockout(LockoutTracker.LOCKOUT_PERMANENT,
+                BiometricPrompt.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
+    }
+
+    private void testMultiBiometricAuth_whenLockout(@LockoutTracker.LockoutMode int lockoutMode,
+            int biometricPromptError) throws Exception {
+        final int[] modalities = new int[] {
+                TYPE_FINGERPRINT,
+                BiometricAuthenticator.TYPE_FACE,
+        };
+
+        final int[] strengths = new int[] {
+                Authenticators.BIOMETRIC_STRONG,
+                Authenticators.BIOMETRIC_STRONG,
+        };
+        setupAuthForMultiple(modalities, strengths);
+
+        when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
+                .thenReturn(lockoutMode);
+        when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+                false /* requireConfirmation */, null /* authenticators */);
+        waitForIdle();
+
+        // The lockout error should be sent, instead of ERROR_NONE_ENROLLED. See b/286923477.
+        verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
+                eq(biometricPromptError), eq(0) /* vendorCode */);
+    }
+
+    @Test
     public void testBiometricOrCredentialAuth_whenBiometricLockout_showsCredential()
             throws Exception {
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
new file mode 100644
index 0000000..0c98c8d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.ITrustManager;
+import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.PromptInfo;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class PreAuthInfoTest {
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private static final int SENSOR_ID_FACE = 1;
+    private static final String TEST_PACKAGE_NAME = "PreAuthInfoTestPackage";
+
+    @Mock
+    IBiometricAuthenticator mFaceAuthenticator;
+    @Mock
+    Context mContext;
+    @Mock
+    ITrustManager mTrustManager;
+    @Mock
+    DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    BiometricService.SettingObserver mSettingObserver;
+    @Mock
+    BiometricSensorPrivacy mBiometricSensorPrivacyUtil;
+
+    @Before
+    public void setup() throws RemoteException {
+        when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
+                .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
+        when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+        when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+        when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
+        when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
+                .thenReturn(LOCKOUT_NONE);
+    }
+
+    @Test
+    public void testFaceAuthentication_whenCameraPrivacyIsEnabled() throws Exception {
+        when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(true);
+
+        BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+                BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+            @Override
+            boolean confirmationAlwaysRequired(int userId) {
+                return false;
+            }
+
+            @Override
+            boolean confirmationSupported() {
+                return false;
+            }
+        };
+        PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+        PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor),
+                0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+                false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil);
+
+        assertThat(preAuthInfo.eligibleSensors).isEmpty();
+    }
+
+    @Test
+    public void testFaceAuthentication_whenCameraPrivacyIsDisabled() throws Exception {
+        when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(false);
+
+        BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+                BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+            @Override
+            boolean confirmationAlwaysRequired(int userId) {
+                return false;
+            }
+
+            @Override
+            boolean confirmationSupported() {
+                return false;
+            }
+        };
+        PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+        PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor),
+                0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+                false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil);
+
+        assertThat(preAuthInfo.eligibleSensors).hasSize(1);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index e9d8269..8929900 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -19,6 +19,8 @@
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
@@ -405,6 +407,59 @@
         testCancelsEnrollWhenRequestId(10L, 20, false /* started */);
     }
 
+    @Test
+    public void testCancelAuthenticationClientWithoutStarting() {
+        final Supplier<Object> lazyDaemon = () -> mock(Object.class);
+        final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
+        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+        final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
+                mToken, callback, mBiometricContext);
+
+        //Schedule authentication client to the pending queue
+        mScheduler.scheduleClientMonitor(client1);
+        mScheduler.scheduleClientMonitor(client2);
+        waitForIdle();
+
+        assertThat(mScheduler.getCurrentClient()).isEqualTo(client1);
+
+        client2.cancel();
+        waitForIdle();
+
+        assertThat(client2.isAlreadyCancelled()).isTrue();
+
+        client1.getCallback().onClientFinished(client1, false);
+        waitForIdle();
+
+        assertThat(mScheduler.getCurrentClient()).isNull();
+    }
+
+    @Test
+    public void testCancelAuthenticationClientWithoutStarting_whenAppCrashes() {
+        final Supplier<Object> lazyDaemon = () -> mock(Object.class);
+        final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
+        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+        final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
+                mToken, callback, mBiometricContext);
+
+        //Schedule authentication client to the pending queue
+        mScheduler.scheduleClientMonitor(client1);
+        mScheduler.scheduleClientMonitor(client2);
+        waitForIdle();
+
+        assertThat(mScheduler.getCurrentClient()).isEqualTo(client1);
+
+        //App crashes
+        client2.binderDied();
+        waitForIdle();
+
+        assertThat(client2.isAlreadyCancelled()).isTrue();
+
+        client1.getCallback().onClientFinished(client1, false);
+        waitForIdle();
+
+        assertThat(mScheduler.getCurrentClient()).isNull();
+    }
+
     private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId,
             boolean started) {
         final Supplier<Object> lazyDaemon = () -> mock(Object.class);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index d5d06d3..046b01c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,8 +16,10 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -205,7 +207,9 @@
         client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
                 true /* authenticated */, new ArrayList<>());
 
-        verify(mCancellationSignal).cancel();
+        verify(mCancellationSignal, never()).cancel();
+        verify(mClientMonitorCallbackConverter)
+                .onError(anyInt(), anyInt(), eq(BIOMETRIC_ERROR_CANCELED), anyInt());
     }
 
     private FaceAuthenticationClient createClient() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index f8f40fe..c383a96 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -399,7 +401,9 @@
 
         mLooper.moveTimeForward(10);
         mLooper.dispatchAll();
-        verify(mCancellationSignal).cancel();
+        verify(mCancellationSignal, never()).cancel();
+        verify(mClientMonitorCallbackConverter)
+                .onError(anyInt(), anyInt(), eq(BIOMETRIC_ERROR_CANCELED), anyInt());
     }
 
     private FingerprintAuthenticationClient createClient() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index aa6ee09..0b13f9a 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -52,7 +52,7 @@
     private static final int SOUND_TRIGGER_MODE = 0; // SOUND_TRIGGER_MODE_ALL_ENABLED
     private static final int DEFAULT_SOUND_TRIGGER_MODE =
             PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
-    private static final String BATTERY_SAVER_CONSTANTS = "disable_vibration=true,"
+    private static final String BATTERY_SAVER_CONSTANTS = "disable_vibration=false,"
             + "advertise_is_enabled=true,"
             + "disable_animation=false,"
             + "enable_firewall=true,"
@@ -117,7 +117,7 @@
 
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() {
-        testServiceDefaultValue_On(ServiceType.VIBRATION);
+        testServiceDefaultValue_Off(ServiceType.VIBRATION);
     }
 
     @SmallTest
@@ -211,7 +211,7 @@
     private void verifyBatterySaverConstantsUpdated() {
         final PowerSaveState vibrationState =
                 mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.VIBRATION);
-        assertThat(vibrationState.batterySaverEnabled).isTrue();
+        assertThat(vibrationState.batterySaverEnabled).isFalse();
 
         final PowerSaveState animationState =
                 mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.ANIMATION);
diff --git a/wifi/java/src/android/net/wifi/nl80211/InstantWifi.java b/wifi/java/src/android/net/wifi/nl80211/InstantWifi.java
new file mode 100644
index 0000000..433e88c
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/nl80211/InstantWifi.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.wifi.nl80211;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class InstantWifi {
+    private static final String INSTANT_WIFI_TAG = "InstantWifi";
+    private static final int OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS = 1000;
+    private static final int WIFI_NETWORK_EXPIRED_MS = 7 * 24 * 60 * 60 * 1000; // a week
+    private static final String NO_CONNECTION_TIMEOUT_ALARM_TAG =
+            INSTANT_WIFI_TAG + " No Connection Timeout";
+
+    private Context mContext;
+    private AlarmManager mAlarmManager;
+    private Handler mEventHandler;
+    private ConnectivityManager mConnectivityManager;
+    private WifiManager mWifiManager;
+    private PowerManager mPowerManager;
+    private long mLastWifiOnSinceBootMs;
+    private long mLastScreenOnSinceBootMs;
+    private boolean mIsWifiConnected = false;
+    private boolean mScreenOn = false;
+    private boolean mWifiEnabled = false;
+    private boolean mIsNoConnectionAlarmSet = false;
+    private ArrayList<WifiNetwork> mConnectedWifiNetworkList = new ArrayList<>();
+    private AlarmManager.OnAlarmListener mNoConnectionTimeoutCallback =
+            new AlarmManager.OnAlarmListener() {
+                public void onAlarm() {
+                    Log.i(INSTANT_WIFI_TAG, "Timed out waiting for wifi connection");
+                    mIsNoConnectionAlarmSet = false;
+                    mWifiManager.startScan();
+                }
+            };
+
+    public InstantWifi(Context context, AlarmManager alarmManager, Handler eventHandler) {
+        mContext = context;
+        mAlarmManager = alarmManager;
+        mEventHandler = eventHandler;
+        mWifiManager = mContext.getSystemService(WifiManager.class);
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+        mConnectivityManager.registerNetworkCallback(
+                new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .build(), new WifiNetworkCallback());
+        // System power service was initialized before wifi nl80211 service.
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        IntentFilter screenEventfilter = new IntentFilter();
+        screenEventfilter.addAction(Intent.ACTION_SCREEN_ON);
+        screenEventfilter.addAction(Intent.ACTION_SCREEN_OFF);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                            if (!mScreenOn) {
+                                mLastScreenOnSinceBootMs = getMockableElapsedRealtime();
+                            }
+                            mScreenOn = true;
+                        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                            mScreenOn = false;
+                        }
+                        Log.d(INSTANT_WIFI_TAG, "mScreenOn is changed to " + mScreenOn);
+                    }
+                }, screenEventfilter, null, mEventHandler);
+        mScreenOn = mPowerManager.isInteractive();
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                                WifiManager.WIFI_STATE_UNKNOWN);
+                        mWifiEnabled = state == WifiManager.WIFI_STATE_ENABLED;
+                        if (mWifiEnabled) {
+                            mLastWifiOnSinceBootMs = getMockableElapsedRealtime();
+                        }
+                        Log.d(INSTANT_WIFI_TAG, "mWifiEnabled is changed to " + mWifiEnabled);
+                    }
+                },
+                new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION),
+                null, mEventHandler);
+    }
+
+    @VisibleForTesting
+    protected long getMockableElapsedRealtime() {
+        return SystemClock.elapsedRealtime();
+    }
+
+    private class WifiNetwork {
+        private final int mNetId;
+        private Set<Integer> mConnectedFrequencies = new HashSet<Integer>();
+        private int[] mLastTwoConnectedFrequencies = new int[2];
+        private long mLastConnectedTimeMillis;
+        WifiNetwork(int netId) {
+            mNetId = netId;
+        }
+
+        public int getNetId() {
+            return mNetId;
+        }
+
+        public boolean addConnectedFrequency(int channelFrequency) {
+            mLastConnectedTimeMillis = getMockableElapsedRealtime();
+            if (mLastTwoConnectedFrequencies[0] != channelFrequency
+                    && mLastTwoConnectedFrequencies[1] != channelFrequency) {
+                mLastTwoConnectedFrequencies[0] = mLastTwoConnectedFrequencies[1];
+                mLastTwoConnectedFrequencies[1] = channelFrequency;
+            }
+            return mConnectedFrequencies.add(channelFrequency);
+        }
+
+        public Set<Integer> getConnectedFrequencies() {
+            return mConnectedFrequencies;
+        }
+
+        public int[] getLastTwoConnectedFrequencies() {
+            if ((getMockableElapsedRealtime() - mLastConnectedTimeMillis)
+                    > WIFI_NETWORK_EXPIRED_MS) {
+                return new int[0];
+            }
+            return mLastTwoConnectedFrequencies;
+        }
+
+        public long getLastConnectedTimeMillis() {
+            return mLastConnectedTimeMillis;
+        }
+    }
+
+    private class WifiNetworkCallback extends NetworkCallback {
+        @Override
+        public void onAvailable(@NonNull Network network) {
+        }
+
+        @Override
+        public void onCapabilitiesChanged(Network network,
+                NetworkCapabilities networkCapabilities) {
+            if (networkCapabilities != null && network != null) {
+                WifiInfo wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
+                if (wifiInfo == null || mWifiManager == null) {
+                    return;
+                }
+                WifiConfiguration config = mWifiManager.getPrivilegedConnectedNetwork();
+                if (config == null) {
+                    return;
+                }
+                final int currentNetworkId = config.networkId;
+                final int connectecFrequency = wifiInfo.getFrequency();
+                if (connectecFrequency < 0 || currentNetworkId < 0) {
+                    return;
+                }
+                mIsWifiConnected = true;
+                if (mIsNoConnectionAlarmSet) {
+                    mAlarmManager.cancel(mNoConnectionTimeoutCallback);
+                }
+                Log.d(INSTANT_WIFI_TAG, "Receive Wifi is connected, freq =  " + connectecFrequency
+                        + " and currentNetworkId : " + currentNetworkId
+                        + ", wifiinfo = " + wifiInfo);
+                boolean isExist = false;
+                for (WifiNetwork wifiNetwork : mConnectedWifiNetworkList) {
+                    if (wifiNetwork.getNetId() == currentNetworkId) {
+                        if (wifiNetwork.addConnectedFrequency(connectecFrequency)) {
+                            Log.d(INSTANT_WIFI_TAG, "Update connected frequency: "
+                                    + connectecFrequency + " to Network currentNetworkId : "
+                                    + currentNetworkId);
+                        }
+                        isExist = true;
+                    }
+                }
+                if (!isExist) {
+                    WifiNetwork currentNetwork = new WifiNetwork(currentNetworkId);
+                    currentNetwork.addConnectedFrequency(connectecFrequency);
+                    if (mConnectedWifiNetworkList.size() < 5) {
+                        mConnectedWifiNetworkList.add(currentNetwork);
+                    } else {
+                        ArrayList<WifiNetwork> lastConnectedWifiNetworkList = new ArrayList<>();
+                        WifiNetwork legacyNetwork = mConnectedWifiNetworkList.get(0);
+                        for (WifiNetwork connectedNetwork : mConnectedWifiNetworkList) {
+                            if (connectedNetwork.getNetId() == legacyNetwork.getNetId()) {
+                                continue;
+                            }
+                            // Keep the used recently network in the last connected list
+                            if (connectedNetwork.getLastConnectedTimeMillis()
+                                    > legacyNetwork.getLastConnectedTimeMillis()) {
+                                lastConnectedWifiNetworkList.add(connectedNetwork);
+                            } else {
+                                lastConnectedWifiNetworkList.add(legacyNetwork);
+                                legacyNetwork = connectedNetwork;
+                            }
+                        }
+                        mConnectedWifiNetworkList = lastConnectedWifiNetworkList;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onLost(@NonNull Network network) {
+            mIsWifiConnected = false;
+        }
+    }
+
+    /**
+     * Returns whether or not the scan freqs should be overrided by using predicted channels.
+     */
+    public boolean isUsePredictedScanningChannels() {
+        if (mIsWifiConnected || mConnectedWifiNetworkList.size() == 0
+                || !mWifiManager.isWifiEnabled() || !mPowerManager.isInteractive()) {
+            return false;
+        }
+        if (!mWifiEnabled || !mScreenOn) {
+            Log.d(INSTANT_WIFI_TAG, "WiFi/Screen State mis-match, run instant Wifi anyway!");
+            return true;
+        }
+        return (((getMockableElapsedRealtime() - mLastWifiOnSinceBootMs)
+                        < OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS)
+                || ((getMockableElapsedRealtime() - mLastScreenOnSinceBootMs)
+                        < OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS));
+    }
+
+    /**
+     * Overrides the frequenies in SingleScanSetting
+     *
+     * @param settings the SingleScanSettings will be overrided.
+     * @param freqs new frequencies of SingleScanSettings
+     */
+    @Nullable
+    public void overrideFreqsForSingleScanSettingsIfNecessary(
+            @Nullable SingleScanSettings settings, @Nullable Set<Integer> freqs) {
+        if (!isUsePredictedScanningChannels() || settings == null || freqs == null
+                || freqs.size() == 0) {
+            return;
+        }
+        if (settings.channelSettings == null) {
+            settings.channelSettings = new ArrayList<>();
+        } else {
+            settings.channelSettings.clear();
+        }
+        for (int freq : freqs) {
+            if (freq > 0) {
+                ChannelSettings channel = new ChannelSettings();
+                channel.frequency = freq;
+                settings.channelSettings.add(channel);
+            }
+        }
+        // Monitor connection after last override scan request.
+        if (mIsNoConnectionAlarmSet) {
+            mAlarmManager.cancel(mNoConnectionTimeoutCallback);
+        }
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                getMockableElapsedRealtime() + OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS,
+                NO_CONNECTION_TIMEOUT_ALARM_TAG, mNoConnectionTimeoutCallback, mEventHandler);
+        mIsNoConnectionAlarmSet = true;
+    }
+
+    /**
+     * Returns the predicted scanning chcnnels set.
+     */
+    @NonNull
+    public Set<Integer> getPredictedScanningChannels() {
+        Set<Integer> predictedScanChannels = new HashSet<>();
+        if (!isUsePredictedScanningChannels()) {
+            Log.d(INSTANT_WIFI_TAG, "Drop, size: " + mConnectedWifiNetworkList.size());
+            return predictedScanChannels;
+        }
+        for (WifiNetwork network : mConnectedWifiNetworkList) {
+            for (int connectedFrequency : network.getLastTwoConnectedFrequencies()) {
+                if (connectedFrequency > 0) {
+                    predictedScanChannels.add(connectedFrequency);
+                    Log.d(INSTANT_WIFI_TAG, "Add channel: " + connectedFrequency
+                            + " to predicted channel");
+                }
+            }
+        }
+        return predictedScanChannels;
+    }
+}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 2a199d2..ca12c4c 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -105,6 +105,8 @@
 
     // Cached wificond binder handlers.
     private IWificond mWificond;
+    private Context mContext;
+    private InstantWifi mInstantWifi;
     private WificondEventHandler mWificondEventHandler = new WificondEventHandler();
     private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
     private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
@@ -174,6 +176,12 @@
 
     /** @hide */
     @VisibleForTesting
+    protected InstantWifi getInstantWifiMockable() {
+        return mInstantWifi;
+    }
+
+    /** @hide */
+    @VisibleForTesting
     public class WificondEventHandler extends IWificondEventCallback.Stub {
         private Map<CountryCodeChangedListener, Executor> mCountryCodeChangedListenerHolder =
                 new HashMap<>();
@@ -419,6 +427,7 @@
     public WifiNl80211Manager(Context context) {
         mAlarmManager = context.getSystemService(AlarmManager.class);
         mEventHandler = new Handler(context.getMainLooper());
+        mContext = context;
     }
 
     /**
@@ -434,6 +443,7 @@
         if (mWificond == null) {
             Log.e(TAG, "Failed to get reference to wificond");
         }
+        mContext = context;
     }
 
     /** @hide */
@@ -441,6 +451,7 @@
     public WifiNl80211Manager(Context context, IWificond wificond) {
         this(context);
         mWificond = wificond;
+        mContext = context;
     }
 
     /** @hide */
@@ -744,6 +755,9 @@
             Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
         }
 
+        if (getInstantWifiMockable() == null) {
+            mInstantWifi = new InstantWifi(mContext, mAlarmManager, mEventHandler);
+        }
         return true;
     }
 
@@ -1071,6 +1085,10 @@
         if (settings == null) {
             return false;
         }
+        if (getInstantWifiMockable() != null) {
+            getInstantWifiMockable().overrideFreqsForSingleScanSettingsIfNecessary(settings,
+                    getInstantWifiMockable().getPredictedScanningChannels());
+        }
         try {
             return scannerImpl.scan(settings);
         } catch (RemoteException e1) {
@@ -1115,6 +1133,10 @@
         if (settings == null) {
             return WifiScanner.REASON_INVALID_ARGS;
         }
+        if (getInstantWifiMockable() != null) {
+            getInstantWifiMockable().overrideFreqsForSingleScanSettingsIfNecessary(settings,
+                    getInstantWifiMockable().getPredictedScanningChannels());
+        }
         try {
             int status = scannerImpl.scanRequest(settings);
             return toFrameworkScanStatusCode(status);
diff --git a/wifi/tests/src/android/net/wifi/nl80211/InstantWifiTest.java b/wifi/tests/src/android/net/wifi/nl80211/InstantWifiTest.java
new file mode 100644
index 0000000..ebff0e2
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/nl80211/InstantWifiTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.wifi.nl80211;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doNothing;
+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.AlarmManager;
+import android.app.test.TestAlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.InstantWifi}.
+ */
+@SmallTest
+public class InstantWifiTest {
+    @Mock private Context mContext;
+    @Mock private ConnectivityManager mMockConnectivityManager;
+    @Mock private WifiManager mMockWifiManager;
+    @Mock private Network mMockWifiNetwork;
+    @Mock private WifiInfo mMockWifiInfo;
+    @Mock private WifiConfiguration mMockWifiConfiguration;
+    @Mock private IPowerManager mPowerManagerService;
+    private InstantWifi mInstantWifi;
+    private TestLooper mLooper;
+    private Handler mHandler;
+    private TestAlarmManager mTestAlarmManager;
+    private AlarmManager mAlarmManager;
+    private PowerManager mMockPowerManager;
+
+    private final ArgumentCaptor<NetworkCallback> mWifiNetworkCallbackCaptor =
+            ArgumentCaptor.forClass(NetworkCallback.class);
+    private final ArgumentCaptor<BroadcastReceiver> mScreenBroadcastReceiverCaptor =
+            ArgumentCaptor.forClass(BroadcastReceiver.class);
+    private final ArgumentCaptor<BroadcastReceiver> mWifiStateBroadcastReceiverCaptor =
+            ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+    private static final int TEST_NETWORK_ID = 1;
+    private static final int TEST_24G_FREQUENCY = 2412;
+    private static final int TEST_5G_FREQUENCY = 5745;
+    private long mTimeOffsetMs = 0;
+
+    private class InstantWifiSpy extends InstantWifi {
+        InstantWifiSpy(Context context, AlarmManager alarmManager, Handler handler) {
+            super(context, alarmManager, handler);
+        }
+
+        @Override
+        protected long getMockableElapsedRealtime() {
+            return mTimeOffsetMs;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mLooper = new TestLooper();
+        mHandler = new Handler(mLooper.getLooper());
+
+        mTestAlarmManager = new TestAlarmManager();
+        mAlarmManager = mTestAlarmManager.getAlarmManager();
+        when(mContext.getSystemServiceName(AlarmManager.class)).thenReturn(Context.ALARM_SERVICE);
+        when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+        when(mContext.getSystemServiceName(WifiManager.class)).thenReturn(Context.WIFI_SERVICE);
+        when(mContext.getSystemService(WifiManager.class)).thenReturn(mMockWifiManager);
+        when(mContext.getSystemServiceName(ConnectivityManager.class))
+                .thenReturn(Context.CONNECTIVITY_SERVICE);
+        when(mContext.getSystemService(ConnectivityManager.class))
+                .thenReturn(mMockConnectivityManager);
+        mMockPowerManager = new PowerManager(mContext, mPowerManagerService,
+                mock(IThermalService.class), mHandler);
+        when(mContext.getSystemServiceName(PowerManager.class)).thenReturn(Context.POWER_SERVICE);
+        when(mContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
+        when(mPowerManagerService.isInteractive()).thenReturn(true);
+
+        doReturn(mMockWifiInfo).when(mMockWifiInfo).makeCopy(anyLong());
+        mTimeOffsetMs = 0;
+        mInstantWifi = new InstantWifiSpy(mContext, mAlarmManager, mHandler);
+        verifyInstantWifiInitialization();
+    }
+
+    private void verifyInstantWifiInitialization() {
+        verify(mMockConnectivityManager).registerNetworkCallback(any(),
+                mWifiNetworkCallbackCaptor.capture());
+        verify(mContext).registerReceiver(mScreenBroadcastReceiverCaptor.capture(),
+                argThat((IntentFilter filter) ->
+                        filter.hasAction(Intent.ACTION_SCREEN_ON)
+                                && filter.hasAction(Intent.ACTION_SCREEN_OFF)), eq(null), any());
+
+        verify(mContext).registerReceiver(mWifiStateBroadcastReceiverCaptor.capture(),
+                argThat((IntentFilter filter) ->
+                        filter.hasAction(WifiManager.WIFI_STATE_CHANGED_ACTION)), eq(null), any());
+    }
+
+    private void mockWifiConnectedEvent(int networkId, int connectedFrequency) {
+        // Send wifi connected event
+        NetworkCapabilities mockWifiNetworkCapabilities =
+                new NetworkCapabilities.Builder().setTransportInfo(mMockWifiInfo).build();
+        mMockWifiConfiguration.networkId = networkId;
+        when(mMockWifiManager.getPrivilegedConnectedNetwork()).thenReturn(mMockWifiConfiguration);
+        when(mMockWifiInfo.getFrequency()).thenReturn(connectedFrequency);
+        mWifiNetworkCallbackCaptor.getValue().onCapabilitiesChanged(mMockWifiNetwork,
+                mockWifiNetworkCapabilities);
+        mLooper.dispatchAll();
+    }
+
+    private void mockWifiOnScreenOnBroadcast(boolean isWifiOn, boolean isScreenOn)
+            throws Exception {
+        // Send Wifi On broadcast
+        Intent wifiOnIntent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        wifiOnIntent.putExtra(WifiManager.EXTRA_WIFI_STATE,
+                isWifiOn ? WifiManager.WIFI_STATE_ENABLED : WifiManager.WIFI_STATE_DISABLED);
+        mWifiStateBroadcastReceiverCaptor.getValue().onReceive(mContext, wifiOnIntent);
+        mLooper.dispatchAll();
+        // Send Screen On broadcast
+        Intent screenOnIntent =
+                new Intent(isScreenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
+        mScreenBroadcastReceiverCaptor.getValue().onReceive(mContext, screenOnIntent);
+        mLooper.dispatchAll();
+        when(mMockWifiManager.isWifiEnabled()).thenReturn(isWifiOn);
+        when(mPowerManagerService.isInteractive()).thenReturn(isScreenOn);
+    }
+
+    @Test
+    public void testisUsePredictedScanningChannels() throws Exception {
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, false /* isScreenOn */);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        mockWifiOnScreenOnBroadcast(false /* isWifiOn */, true /* isScreenOn */);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        // Send wifi connected event
+        mockWifiConnectedEvent(TEST_NETWORK_ID, TEST_24G_FREQUENCY);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        // Send wifi disconnect
+        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
+        assertTrue(mInstantWifi.isUsePredictedScanningChannels());
+        // Shift time to make it expired
+        mTimeOffsetMs = 1100;
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+    }
+
+    @Test
+    public void testGetPredictedScanningChannels() throws Exception {
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        // Send wifi connected event on T0
+        mockWifiConnectedEvent(TEST_NETWORK_ID, TEST_24G_FREQUENCY);
+        // Send wifi disconnect
+        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
+        assertTrue(mInstantWifi.isUsePredictedScanningChannels());
+        assertTrue(mInstantWifi.getPredictedScanningChannels().contains(TEST_24G_FREQUENCY));
+        mTimeOffsetMs += 1000; // T1 = 1000 ms
+        // Send wifi connected event
+        mockWifiConnectedEvent(TEST_NETWORK_ID + 1, TEST_5G_FREQUENCY);
+        // Send wifi disconnect
+        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
+        // isUsePredictedScanningChannels is false since wifi on & screen on is expired
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        // Override the Wifi On & Screen on time
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        assertTrue(mInstantWifi.getPredictedScanningChannels().contains(TEST_5G_FREQUENCY));
+        mTimeOffsetMs += 7 * 24 * 60 * 60 * 1000; // Make T0 expired
+        // Override the Wifi On & Screen on time
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        assertFalse(mInstantWifi.getPredictedScanningChannels().contains(TEST_24G_FREQUENCY));
+        assertTrue(mInstantWifi.getPredictedScanningChannels().contains(TEST_5G_FREQUENCY));
+    }
+
+    @Test
+    public void testOverrideFreqsForSingleScanSettings() throws Exception {
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        // Send wifi connected event
+        mockWifiConnectedEvent(TEST_NETWORK_ID, TEST_24G_FREQUENCY);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        // Send wifi disconnect
+        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
+        assertTrue(mInstantWifi.isUsePredictedScanningChannels());
+
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), any());
+        Set<Integer> testFreqs = Set.of(
+                TEST_24G_FREQUENCY, TEST_5G_FREQUENCY);
+        SingleScanSettings testSingleScanSettings = new SingleScanSettings();
+        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(
+                testSingleScanSettings, new HashSet<Integer>());
+        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(
+                testSingleScanSettings, null);
+        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(null, null);
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), any(), any(), any());
+        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(testSingleScanSettings,
+                testFreqs);
+        verify(mAlarmManager).set(anyInt(), anyLong(), any(), any(), any());
+        Set<Integer> overridedFreqs = new HashSet<Integer>();
+        for (ChannelSettings channel : testSingleScanSettings.channelSettings) {
+            overridedFreqs.add(channel.frequency);
+        }
+        assertEquals(testFreqs, overridedFreqs);
+        alarmListenerCaptor.getValue().onAlarm();
+        verify(mMockWifiManager).startScan();
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 362eb14..f12818f 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -26,6 +27,7 @@
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -37,6 +39,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AlarmManager;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.app.test.TestAlarmManager;
 import android.content.Context;
 import android.net.MacAddress;
@@ -104,6 +107,9 @@
     private WifiNl80211Manager.CountryCodeChangedListener mCountryCodeChangedListener2;
     @Mock
     private Context mContext;
+    @Mock
+    private InstantWifi mMockInstantWifi;
+
     private TestLooper mLooper;
     private TestAlarmManager mTestAlarmManager;
     private AlarmManager mAlarmManager;
@@ -167,6 +173,17 @@
             0x00, 0x00
     };
 
+    private class WifiNl80211ManagerSpy extends WifiNl80211Manager {
+        WifiNl80211ManagerSpy(Context context, IWificond wificond) {
+            super(context, wificond);
+        }
+
+        @Override
+        protected InstantWifi getInstantWifiMockable() {
+            return mMockInstantWifi;
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         // Setup mocks for successful WificondControl operation. Failure case mocks should be
@@ -181,6 +198,8 @@
         mLooper = new TestLooper();
         when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
 
+        doNothing().when(mMockInstantWifi).overrideFreqsForSingleScanSettingsIfNecessary(
+                any(), any());
         when(mWificond.asBinder()).thenReturn(mWifiCondBinder);
         when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
         when(mWificond.createClientInterface(any())).thenReturn(mClientInterface);
@@ -189,7 +208,7 @@
         when(mWificond.tearDownApInterface(any())).thenReturn(true);
         when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
         when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
-        mWificondControl = new WifiNl80211Manager(mContext, mWificond);
+        mWificondControl = new WifiNl80211ManagerSpy(mContext, mWificond);
         mWificondEventHandler = mWificondControl.getWificondEventHandler();
         assertEquals(true,
                 mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
@@ -1159,6 +1178,40 @@
         verify(mWificond).notifyCountryCodeChanged();
     }
 
+    @Test
+    public void testInstantWifi() throws Exception {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(SingleScanSettings settings, Set<Integer> freqs) {
+                if (settings.channelSettings == null) {
+                    settings.channelSettings = new ArrayList<>();
+                } else {
+                    settings.channelSettings.clear();
+                }
+                for (int freq : freqs) {
+                    if (freq > 0) {
+                        ChannelSettings channel = new ChannelSettings();
+                        channel.frequency = freq;
+                        settings.channelSettings.add(channel);
+                    }
+                }
+            }
+        }).when(mMockInstantWifi).overrideFreqsForSingleScanSettingsIfNecessary(
+                any(), any());
+        Set<Integer> testPredictedChannelsSet = Set.of(2412, 5745);
+        assertNotEquals(testPredictedChannelsSet, SCAN_FREQ_SET);
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        when(mMockInstantWifi.getPredictedScanningChannels()).thenReturn(testPredictedChannelsSet);
+
+        // Trigger scan to check scan settings are changed
+        assertTrue(mWificondControl.startScan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+        verify(mMockInstantWifi).getPredictedScanningChannels();
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+                IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+                testPredictedChannelsSet, SCAN_HIDDEN_NETWORK_SSID_LIST, false, null)));
+    }
+
     // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it
     // matches the provided frequency set and ssid set.
     private class ScanMatcher implements ArgumentMatcher<SingleScanSettings> {