diff options
201 files changed, 4662 insertions, 981 deletions
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index e23bbc6c347d..d3bde4b4b8a8 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -20,6 +20,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainRunna 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 @@ public class CameraDeviceImpl extends CameraDevice } } + // 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/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 01977f6195ff..619544366b02 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -103,6 +103,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing private static final int MSG_UDFPS_POINTER_DOWN = 108; private static final int MSG_UDFPS_POINTER_UP = 109; private static final int MSG_POWER_BUTTON_PRESSED = 110; + private static final int MSG_UDFPS_OVERLAY_SHOWN = 111; /** * @hide @@ -121,6 +122,24 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing public @interface EnrollReason {} /** + * Udfps ui event of overlay is shown on the screen. + * @hide + */ + public static final int UDFPS_UI_OVERLAY_SHOWN = 1; + /** + * Udfps ui event of the udfps UI being ready (e.g. HBM illumination is enabled). + * @hide + */ + public static final int UDFPS_UI_READY = 2; + + /** + * @hide + */ + @IntDef({UDFPS_UI_OVERLAY_SHOWN, UDFPS_UI_READY}) + @Retention(RetentionPolicy.SOURCE) + public @interface UdfpsUiEvent{} + + /** * Request authentication with any single sensor. * @hide */ @@ -475,12 +494,17 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Called when a pointer down event has occurred. */ - public void onPointerDown(int sensorId){ } + public void onUdfpsPointerDown(int sensorId){ } /** * Called when a pointer up event has occurred. */ - public void onPointerUp(int sensorId){ } + public void onUdfpsPointerUp(int sensorId){ } + + /** + * Called when udfps overlay is shown. + */ + public void onUdfpsOverlayShown() { } } /** @@ -1112,14 +1136,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public void onUiReady(long requestId, int sensorId) { + public void onUdfpsUiEvent(@UdfpsUiEvent int event, long requestId, int sensorId) { if (mService == null) { - Slog.w(TAG, "onUiReady: no fingerprint service"); + Slog.w(TAG, "onUdfpsUiEvent: no fingerprint service"); return; } try { - mService.onUiReady(requestId, sensorId); + mService.onUdfpsUiEvent(event, requestId, sensorId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1365,6 +1389,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing case MSG_POWER_BUTTON_PRESSED: sendPowerPressed(); break; + case MSG_UDFPS_OVERLAY_SHOWN: + sendUdfpsOverlayShown(); default: Slog.w(TAG, "Unknown message: " + msg.what); @@ -1489,7 +1515,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } if (mEnrollmentCallback != null) { - mEnrollmentCallback.onPointerDown(sensorId); + mEnrollmentCallback.onUdfpsPointerDown(sensorId); } } @@ -1500,7 +1526,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing mAuthenticationCallback.onUdfpsPointerUp(sensorId); } if (mEnrollmentCallback != null) { - mEnrollmentCallback.onPointerUp(sensorId); + mEnrollmentCallback.onUdfpsPointerUp(sensorId); } } @@ -1512,6 +1538,12 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } + private void sendUdfpsOverlayShown() { + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onUdfpsOverlayShown(); + } + } + /** * @hide */ @@ -1787,6 +1819,11 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing public void onUdfpsPointerUp(int sensorId) { mHandler.obtainMessage(MSG_UDFPS_POINTER_UP, sensorId, 0).sendToTarget(); } + + @Override + public void onUdfpsOverlayShown() { + mHandler.obtainMessage(MSG_UDFPS_OVERLAY_SHOWN).sendToTarget(); + } }; } diff --git a/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java b/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java index a9779b51321b..89d710d4adfe 100644 --- a/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java +++ b/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java @@ -75,4 +75,9 @@ public class FingerprintServiceReceiver extends IFingerprintServiceReceiver.Stub public void onUdfpsPointerUp(int sensorId) throws RemoteException { } + + @Override + public void onUdfpsOverlayShown() throws RemoteException { + + } } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index ec5749ed4f05..ff2f313ac3df 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -193,7 +193,7 @@ interface IFingerprintService { // Notifies about the fingerprint UI being ready (e.g. HBM illumination is enabled). @EnforcePermission("USE_BIOMETRIC_INTERNAL") - void onUiReady(long requestId, int sensorId); + void onUdfpsUiEvent(int event, long requestId, int sensorId); // Sets the controller for managing the UDFPS overlay. @EnforcePermission("USE_BIOMETRIC_INTERNAL") diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl index 9cea1fed629d..91a32d78314c 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl @@ -32,4 +32,5 @@ oneway interface IFingerprintServiceReceiver { void onChallengeGenerated(int sensorId, int userId, long challenge); void onUdfpsPointerDown(int sensorId); void onUdfpsPointerUp(int sensorId); + void onUdfpsOverlayShown(); } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index a95ce64ecec8..7f53cb433c98 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -771,7 +771,7 @@ public class ZygoteInit { 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 ec67aa86bcc9..726c25540e6f 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 6a7c06581d8a..a941dc7a7f99 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 ecab978bee6c..f97768ebdd16 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 87df6bb1e466..a812ac0932da 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 2542163edfc0..626c3d6e930a 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 3d7e48779ce8..5a41074a0cf7 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 1e731e8813ea..1baf387eeab9 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 f8b6daa797d4..f226b966bb38 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 65c883a6eab5..b4ba000938ce 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 fe686db71c4e..25c0d42853e8 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 63fff4f2a920..e824ecc213ca 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 b581d49384b2..46fc3a3a1114 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 fc9786769799..196fa5618db3 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 43f6a8cdae03..c1f0e711c1d3 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 55e845586ef1..ad6cfbf6042e 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 5f7306ade927..4ea1eab5cd74 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 73b2968ab9cc..675add93a146 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 73017c11ac25..7b6ccf31f886 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 aeabd0096974..a1c7872e5110 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 92f989728902..7c467e8c841f 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 b560c017b7f8..4f6c12069810 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 b39f86231e19..93430602870d 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 4b58a6f94ee9..c0e705cd6750 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 e4c99482166d..c50f3ba19831 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 a165af18472e..51f41ef5535b 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 1813fc4f0439..b5942580e99f 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 5f120c58d766..edfae7648bb9 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 e72b41e2bf39..bca6087c94a4 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 c162672c7ce0..221c526732ee 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 18fd29270d97..39ae24323c41 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 ab3859e39800..703bb440f403 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 4c66168f36a8..bfa4f3943953 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 eb8d11118970..d655dd4beed0 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 793fc8699d1e..e5efd16f3b3c 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 da1b5dc71889..f831700c5721 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 af5e5fe1d70f..92d85c7a8ad3 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 b43935f17844..1c7f5b3e0352 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 f34b103360cd..fe663ddebe71 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 cc032849fc28..1bba6e51580c 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 d8d721c0b741..64eb303c2e20 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 c7fa7d0b2671..e09aa157a328 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 29902e966847..70f5828a2b6f 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 340135e8f385..864778597ca5 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 1f0cbfcd4767..26aa63d0396b 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 e6979ae283f8..18fe689d4466 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 80a0c7cf2a29..ede68e4978fd 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 15517b5ee3e3..b600d2edcbd1 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 6a12170004c8..e3f1952f3ec2 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 fade02ed8dac..d874fddf5d5a 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 1f2341dd5387..06a35302ffbd 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 c3b86e382399..0ca9b379cbb1 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 b69fb83dde35..8df63ece2264 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 5f6b35dfb887..432ffce831b2 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 f62c70690f05..ec737c916c92 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 5a849570766c..2df17b48f605 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 15ef328cfbb0..f8c8aaad345b 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 92ee0a1cad69..bbb19925b661 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 80082c86cc7b..5457872a29f6 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 81a7bb4b6cb1..6932b441b5ef 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 9c61e5b3f03c..0d0e0c899f6d 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 7a8684f4e8bc..f72ff68499fa 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 0a6f769a13dc..72cb00e15929 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 96242e16dcae..2158dfdc20e5 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 0a6f769a13dc..72cb00e15929 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 5edccb4f24c5..b52c0eb6d33c 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 e48c48711a3b..f8f323ffc805 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 0df5c652a35a..a62caebb5966 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 f13ef2b2e120..9c3c655710cb 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 4647ed9fba7b..f92a5bd233e4 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 82d92c1d9039..512dbc7b37b6 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 ef2456e24bae..ccd638e058de 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 c40b2055986d..4b5d45e79d75 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 caf09cbd81c6..5e30e0d29bc9 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 4c765ed52773..26442bf553e9 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 89468860c141..73aab5823671 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 107a66a77710..916e857acc77 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 549882b8480d..786c324642a8 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 33b66d31cd84..a0c71b5698b9 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 5dd6037bac73..0c9f99a664f1 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 e692be7d9f7c..e1c275296885 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 bb9cbd28b680..93ad0ceec86b 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 83730d5862df..bfe15619d87f 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 80558133a287..d7101f417c9f 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 305560032d00..a471a42c5d37 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 f26cbe8d20a2..1449ab6eb5b3 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 270cc4bf1d61..72b440499996 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 fd7418542a2b..4ae54a052859 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 a5b2b853fddd..04fef58e973e 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 dc4eafd2e00e..80a997717cc3 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/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp index b4c67ccda6f2..f9e64aee1513 100644 --- a/packages/SettingsLib/Spa/tests/Android.bp +++ b/packages/SettingsLib/Spa/tests/Android.bp @@ -31,7 +31,6 @@ android_test { "SpaLib", "SpaLibTestUtils", "androidx.compose.runtime_runtime", - "androidx.lifecycle_lifecycle-runtime-testing", "androidx.test.ext.junit", "androidx.test.runner", "mockito-target-minus-junit4", diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp index 2c1e1c2abc2c..e4d56cc4f2a0 100644 --- a/packages/SettingsLib/Spa/testutils/Android.bp +++ b/packages/SettingsLib/Spa/testutils/Android.bp @@ -29,6 +29,7 @@ android_library { "androidx.compose.runtime_runtime", "androidx.compose.ui_ui-test-junit4", "androidx.compose.ui_ui-test-manifest", + "androidx.lifecycle_lifecycle-runtime-testing", "mockito", "truth-prebuilt", ], 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 c9d9b57c7170..5b7899b1e3af 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 af711ddd59c2..cb157734aa6a 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 000000000000..1c14c0a72ce4 --- /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 000000000000..5d6e6a3adeb7 --- /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 2945d5c1099a..3aa6fcb06016 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 @@ package com.android.settingslib.drawer; 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 000000000000..47eb31cefa29 --- /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 54da585aba7a..b775e93bf69c 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 @@ public class ProviderTile extends Tile { 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 23669b2743ce..a1a4e5867299 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 class SwitchController { 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 @@ public abstract class SwitchController { */ 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); - } - } - - /** - * 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); - } - } - - /** - * Notify registered observers that checked state was updated and attempt to sync changes. - */ - public void notifyCheckedChanged(Context context) { - notifyChanged(context, METHOD_IS_CHECKED); + @Override + public String getKey() { + return getSwitchKey(); } - void setAuthority(String authority) { - mAuthority = authority; + @Override + public boolean isSwitchChecked() { + return isChecked(); } - 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; + @Override + public boolean onSwitchCheckedChanged(boolean checked) { + return onCheckedChanged(checked); } - private void notifyChanged(Context context, String method) { - final Uri uri = TileUtils.buildUri(mAuthority, method, getSwitchKey()); - context.getContentResolver().notifyChange(uri, null); + @Override + public String getSwitchErrorMessage(boolean attemptedChecked) { + return getErrorMessage(attemptedChecked); } /** - * Collects all meta data of the item. + * Same as {@link EntryController.MetaData}, for backwards compatibility purpose. + * + * @deprecated Use {@link EntryController.MetaData} instead. */ - 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 f2b3e30dc252..ad00ced8a3ac 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 @@ public abstract class SwitchesProvider extends ContentProvider { 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 a0c8ac4e0a51..00dd8cc88da2 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 @@ package com.android.settingslib.drawer; 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.META_DATA_PREFERENCE_TITL 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 androidx.annotation.VisibleForTesting; 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 abstract class Tile implements Parcelable { */ public ArrayList<UserHandle> userHandle = new ArrayList<>(); + public HashMap<UserHandle, PendingIntent> pendingIntentMap = new HashMap<>(); + @VisibleForTesting long mLastUpdateTime; private final String mComponentPackage; @@ -186,6 +191,13 @@ public abstract class Tile implements Parcelable { } /** + * 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 @@ public abstract class Tile implements Parcelable { 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 acc0087f6dcf..e46db75f633e 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 class TileUtils { 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 @@ public class TileUtils { "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 @@ public class TileUtils { 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 @@ public class TileUtils { 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/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 2e6bb535a8f0..f522fd13c9f8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -583,7 +583,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> */ public void setName(String name) { // Prevent getName() to be set to null if setName(null) is called - if (name == null || TextUtils.equals(name, getName())) { + if (TextUtils.isEmpty(name) || TextUtils.equals(name, getName())) { return; } mDevice.setAlias(name); 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 09abc394634a..9ee8a32fdc77 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 @@ public class MetricsFeatureProvider { } 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 @@ public class MetricsFeatureProvider { 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/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 6444f3bd4341..4b61ff1177bd 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -1015,6 +1015,13 @@ public class CachedBluetoothDeviceTest { } @Test + public void setName_setDeviceNameIsEmpty() { + mCachedDevice.setName(""); + + verify(mDevice, never()).setAlias(any()); + } + + @Test public void getProfileConnectionState_nullProfile_returnDisconnected() { assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo( BluetoothProfile.STATE_DISCONNECTED); 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 3352d86b2dcc..dd8d54a62ff4 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 @@ public class MetricsFeatureProviderTest { 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 aa6b0bf33b69..4d2b1ae2ade0 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 @@ package com.android.settingslib.drawer; 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 @@ public class ActivityTileTest { 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 000000000000..a2483305c94a --- /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 abfb407d749e..80f9efb8b5ac 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 @@ package com.android.settingslib.drawer; 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 @@ public class ProviderTileTest { 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 906e06e81e2b..20864664e512 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_KEY_PROFILE; 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.mockito.Mockito.when; 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 @@ public class TileUtilsTest { 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 @@ public class TileUtilsTest { 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 7f706859abb3..d8348eda3d97 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 @@ public class AuthContainerView extends LinearLayout 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 57f1928fe545..5b746f1b424d 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 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, 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 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, 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 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, 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/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java index 6db266f4f1cb..9d8dcc1efdd8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java @@ -21,6 +21,8 @@ import static android.app.PendingIntent.FLAG_IMMUTABLE; import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG; import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -30,6 +32,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricSourceType; +import android.hardware.biometrics.BiometricStateListener; +import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; @@ -42,7 +47,6 @@ import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.policy.KeyguardStateController; - import java.util.Optional; import javax.inject.Inject; @@ -69,6 +73,8 @@ public class BiometricNotificationService implements CoreStartable { private final NotificationManager mNotificationManager; private final BiometricNotificationBroadcastReceiver mBroadcastReceiver; private final FingerprintReEnrollNotification mFingerprintReEnrollNotification; + private final FingerprintManager mFingerprintManager; + private final FaceManager mFaceManager; private NotificationChannel mNotificationChannel; private boolean mFaceNotificationQueued; private boolean mFingerprintNotificationQueued; @@ -119,14 +125,29 @@ public class BiometricNotificationService implements CoreStartable { } }; + private final BiometricStateListener mFaceStateListener = new BiometricStateListener() { + @Override + public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { + mNotificationManager.cancelAsUser(TAG, FACE_NOTIFICATION_ID, UserHandle.CURRENT); + } + }; + + private final BiometricStateListener mFingerprintStateListener = new BiometricStateListener() { + @Override + public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { + mNotificationManager.cancelAsUser(TAG, FINGERPRINT_NOTIFICATION_ID, UserHandle.CURRENT); + } + }; @Inject - public BiometricNotificationService(Context context, - KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardStateController keyguardStateController, - Handler handler, NotificationManager notificationManager, - BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver, - Optional<FingerprintReEnrollNotification> fingerprintReEnrollNotification) { + public BiometricNotificationService(@NonNull Context context, + @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, + @NonNull KeyguardStateController keyguardStateController, + @NonNull Handler handler, @NonNull NotificationManager notificationManager, + @NonNull BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver, + @NonNull Optional<FingerprintReEnrollNotification> fingerprintReEnrollNotification, + @Nullable FingerprintManager fingerprintManager, + @Nullable FaceManager faceManager) { mContext = context; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mKeyguardStateController = keyguardStateController; @@ -135,6 +156,8 @@ public class BiometricNotificationService implements CoreStartable { mBroadcastReceiver = biometricNotificationBroadcastReceiver; mFingerprintReEnrollNotification = fingerprintReEnrollNotification.orElse( new FingerprintReEnrollNotificationImpl()); + mFingerprintManager = fingerprintManager; + mFaceManager = faceManager; } @Override @@ -148,9 +171,16 @@ public class BiometricNotificationService implements CoreStartable { intentFilter.addAction(ACTION_SHOW_FACE_REENROLL_DIALOG); mContext.registerReceiver(mBroadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED_UNAUDITED); + if (mFingerprintManager != null) { + mFingerprintManager.registerBiometricStateListener(mFingerprintStateListener); + } + if (mFaceManager != null) { + mFaceManager.registerBiometricStateListener(mFaceStateListener); + } } private void queueFaceReenrollNotification() { + Log.d(TAG, "Face re-enroll notification queued."); mFaceNotificationQueued = true; final String title = mContext.getString(R.string.face_re_enroll_notification_title); final String content = mContext.getString( @@ -163,6 +193,7 @@ public class BiometricNotificationService implements CoreStartable { } private void queueFingerprintReenrollNotification() { + Log.d(TAG, "Fingerprint re-enroll notification queued."); mFingerprintNotificationQueued = true; final String title = mContext.getString(R.string.fingerprint_re_enroll_notification_title); final String content = mContext.getString( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 10e45dadee60..16d12bbfe3b4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -188,6 +188,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { @Nullable private VelocityTracker mVelocityTracker; // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active. private int mActivePointerId = -1; + // Whether a pointer has been pilfered for current gesture + private boolean mPointerPilfered = false; // The timestamp of the most recent touch log. private long mTouchLogTime; // The timestamp of the most recent log of a touch InteractionEvent. @@ -354,7 +356,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { UdfpsController.this.mAlternateTouchProvider.onUiReady(); } else { final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L; - UdfpsController.this.mFingerprintManager.onUiReady(requestId, sensorId); + UdfpsController.this.mFingerprintManager.onUdfpsUiEvent( + FingerprintManager.UDFPS_UI_READY, requestId, sensorId); } } } @@ -557,6 +560,11 @@ public class UdfpsController implements DozeReceiver, Dumpable { || mPrimaryBouncerInteractor.isInTransit()) { return false; } + if (event.getAction() == MotionEvent.ACTION_DOWN + || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { + // Reset on ACTION_DOWN, start of new gesture + mPointerPilfered = false; + } final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId, mOverlayParams); @@ -636,10 +644,11 @@ public class UdfpsController implements DozeReceiver, Dumpable { shouldPilfer = true; } - // Execute the pilfer - if (shouldPilfer) { + // Pilfer only once per gesture + if (shouldPilfer && !mPointerPilfered) { mInputManager.pilferPointers( mOverlay.getOverlayView().getViewRootImpl().getInputToken()); + mPointerPilfered = true; } return processedTouch.getTouchData().isWithinBounds(mOverlayParams.getNativeSensorBounds()); @@ -958,6 +967,10 @@ public class UdfpsController implements DozeReceiver, Dumpable { mOnFingerDown = false; mAttemptedToDismissKeyguard = false; mOrientationListener.enable(); + if (mFingerprintManager != null) { + mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN, + overlay.getRequestId(), mSensorProps.sensorId); + } } else { Log.v(TAG, "showUdfpsOverlay | the overlay is already showing"); } @@ -1099,7 +1112,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE); }); } else { - mFingerprintManager.onUiReady(requestId, mSensorProps.sensorId); + mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId, + mSensorProps.sensorId); mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE); } } 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 ddf1457e385c..a5e846ad61ca 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 @@ interface BiometricsModule { @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 000000000000..3d5ed823f771 --- /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 000000000000..68c4a10fcfad --- /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 b4dc272b71da..b35fbbc7bb32 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.Flow 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 @@ interface PromptRepository { * * 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 @@ interface PromptRepository { userId: Int, gatekeeperChallenge: Long?, kind: PromptKind, - requireConfirmation: Boolean = false, ) /** Unset the prompt info. */ @@ -56,8 +75,12 @@ interface PromptRepository { } @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 @@ class PromptRepositoryImpl @Inject constructor(private val authController: AuthC 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 @@ class PromptRepositoryImpl @Inject constructor(private val authController: AuthC _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 e6e07f9d7794..be99dd92f1bd 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 @@ interface PromptSelectorInteractor { */ 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 @@ constructor( } } - 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 @@ constructor( override fun useBiometricsForAuthentication( promptInfo: PromptInfo, - requireConfirmation: Boolean, userId: Int, challenge: Long, modalities: BiometricModalities @@ -152,7 +151,6 @@ constructor( 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 8486c3f96b21..6a7431e54034 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 @@ object BiometricViewBinder { 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 @@ object BiometricViewBinder { 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 @@ private class Spaghetti( } 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 1dffa80a084f..1a286cf57982 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.view.accessibility.AccessibilityManager 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 @@ object BiometricViewSizeBinder { // 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 @@ object BiometricViewSizeBinder { 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 9cb91b3d51a7..444082ca2742 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 @@ data class PromptAuthState( 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 @@ data class PromptAuthState( 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 2f8ed096f4ba..05a536236de5 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 @@ constructor( /** 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 @@ constructor( _forceLargeSize, _forceMediumSize, modalities, - interactor.isConfirmationRequested, + interactor.isConfirmationRequired, fingerprintStartMode, ) { forceLarge, forceMedium, modalities, confirmationRequired, fpStartMode -> when { @@ -383,7 +386,7 @@ constructor( 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 @@ constructor( 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 a431a59fcef6..a90980fddfb0 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 @@ import dagger.Subcomponent; 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 951d077808c9..31f7e88daa04 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 @@ object Flags { // TODO(b/267722622): Tracking Bug @JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = - unreleasedFlag( + releasedFlag( 229, "wallpaper_picker_ui_for_aiwp" ) @@ -662,7 +662,7 @@ object Flags { @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 290bf0d0734c..c5027cc511a4 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 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks 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 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks mCommandQueue = commandQueue; mBlurUtils = blurUtils; mCommandQueue.addCallback(this); + mShutdownUi = shutdownUi; } @Override @@ -80,103 +68,8 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks @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(); - } - - @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; - } + mShutdownUi.showShutdownUi(isReboot, reason); } - - @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 000000000000..68dc1b3dc7d7 --- /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 000000000000..b7285da49bb7 --- /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 640adcc9dd94..5e489b0f38ac 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.DefaultComponentBinder; 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 @@ import dagger.Subcomponent; 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/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java index 38c9caf085e2..9cb3b1aa9a55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java @@ -30,7 +30,11 @@ import android.app.Notification; import android.app.NotificationManager; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricSourceType; +import android.hardware.biometrics.BiometricStateListener; +import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; import android.os.Handler; +import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -69,6 +73,10 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { Optional<FingerprintReEnrollNotification> mFingerprintReEnrollNotificationOptional; @Mock FingerprintReEnrollNotification mFingerprintReEnrollNotification; + @Mock + FingerprintManager mFingerprintManager; + @Mock + FaceManager mFaceManager; private static final String TAG = "BiometricNotificationService"; private static final int FACE_NOTIFICATION_ID = 1; @@ -81,6 +89,8 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { private TestableLooper mLooper; private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback; private KeyguardStateController.Callback mKeyguardStateControllerCallback; + private BiometricStateListener mFaceStateListener; + private BiometricStateListener mFingerprintStateListener; @Before public void setUp() { @@ -99,25 +109,37 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { mKeyguardUpdateMonitor, mKeyguardStateController, handler, mNotificationManager, broadcastReceiver, - mFingerprintReEnrollNotificationOptional); + mFingerprintReEnrollNotificationOptional, + mFingerprintManager, + mFaceManager); biometricNotificationService.start(); ArgumentCaptor<KeyguardUpdateMonitorCallback> updateMonitorCallbackArgumentCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); ArgumentCaptor<KeyguardStateController.Callback> stateControllerCallbackArgumentCaptor = ArgumentCaptor.forClass(KeyguardStateController.Callback.class); + ArgumentCaptor<BiometricStateListener> faceStateListenerArgumentCaptor = + ArgumentCaptor.forClass(BiometricStateListener.class); + ArgumentCaptor<BiometricStateListener> fingerprintStateListenerArgumentCaptor = + ArgumentCaptor.forClass(BiometricStateListener.class); verify(mKeyguardUpdateMonitor).registerCallback( updateMonitorCallbackArgumentCaptor.capture()); verify(mKeyguardStateController).addCallback( stateControllerCallbackArgumentCaptor.capture()); + verify(mFaceManager).registerBiometricStateListener( + faceStateListenerArgumentCaptor.capture()); + verify(mFingerprintManager).registerBiometricStateListener( + fingerprintStateListenerArgumentCaptor.capture()); + mFaceStateListener = faceStateListenerArgumentCaptor.getValue(); + mFingerprintStateListener = fingerprintStateListenerArgumentCaptor.getValue(); mKeyguardUpdateMonitorCallback = updateMonitorCallbackArgumentCaptor.getValue(); mKeyguardStateControllerCallback = stateControllerCallbackArgumentCaptor.getValue(); } @Test - public void testShowFingerprintReEnrollNotification() { + public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll() { when(mKeyguardStateController.isShowing()).thenReturn(false); mKeyguardUpdateMonitorCallback.onBiometricHelp( @@ -139,7 +161,7 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { .isEqualTo(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG); } @Test - public void testShowFaceReEnrollNotification() { + public void testShowFaceReEnrollNotification_onErrorReEnroll() { when(mKeyguardStateController.isShowing()).thenReturn(false); mKeyguardUpdateMonitorCallback.onBiometricError( @@ -161,4 +183,52 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { .isEqualTo(ACTION_SHOW_FACE_REENROLL_DIALOG); } + @Test + public void testCancelReEnrollmentNotification_onFaceEnrollmentStateChange() { + when(mKeyguardStateController.isShowing()).thenReturn(false); + + mKeyguardUpdateMonitorCallback.onBiometricError( + BiometricFaceConstants.BIOMETRIC_ERROR_RE_ENROLL, + "Testing Face Re-enrollment" /* errString */, + BiometricSourceType.FACE + ); + mKeyguardStateControllerCallback.onKeyguardShowingChanged(); + + mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS); + mLooper.processAllMessages(); + + verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID), + mNotificationArgumentCaptor.capture(), any()); + + mFaceStateListener.onEnrollmentsChanged(0 /* userId */, 0 /* sensorId */, + false /* hasEnrollments */); + + verify(mNotificationManager).cancelAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID), + eq(UserHandle.CURRENT)); + } + + @Test + public void testCancelReEnrollmentNotification_onFingerprintEnrollmentStateChange() { + when(mKeyguardStateController.isShowing()).thenReturn(false); + + mKeyguardUpdateMonitorCallback.onBiometricHelp( + FINGERPRINT_ACQUIRED_RE_ENROLL, + "Testing Fingerprint Re-enrollment" /* errString */, + BiometricSourceType.FINGERPRINT + ); + mKeyguardStateControllerCallback.onKeyguardShowingChanged(); + + mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS); + mLooper.processAllMessages(); + + verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID), + mNotificationArgumentCaptor.capture(), any()); + + mFingerprintStateListener.onEnrollmentsChanged(0 /* userId */, 0 /* sensorId */, + false /* hasEnrollments */); + + verify(mNotificationManager).cancelAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID), + eq(UserHandle.CURRENT)); + } + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 78341915edb7..7578cc774c48 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -442,6 +442,16 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void showUdfpsOverlay_callsListener() throws RemoteException { + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mFingerprintManager).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN, + TEST_REQUEST_ID, mOpticalProps.sensorId); + } + + @Test public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception { mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); @@ -761,17 +771,20 @@ public class UdfpsControllerTest extends SysuiTestCase { inOrder.verify(mAlternateTouchProvider).onUiReady(); inOrder.verify(mLatencyTracker).onActionEnd( eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt()); + verify(mFingerprintManager, never()).onUdfpsUiEvent( + eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt()); } else { InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker); - inOrder.verify(mFingerprintManager).onUiReady(eq(TEST_REQUEST_ID), + inOrder.verify(mFingerprintManager).onUdfpsUiEvent( + eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID), eq(testParams.sensorProps.sensorId)); inOrder.verify(mLatencyTracker).onActionEnd( eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); verify(mAlternateTouchProvider, never()).onUiReady(); } } else { - verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt()); + verify(mFingerprintManager, never()).onUdfpsUiEvent( + eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt()); verify(mAlternateTouchProvider, never()).onUiReady(); verify(mLatencyTracker, never()).onActionEnd( eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); @@ -1327,12 +1340,22 @@ public class UdfpsControllerTest extends SysuiTestCase { // WHEN ACTION_DOWN is received when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( processorResultDown); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); - downEvent.recycle(); - // THEN the touch is pilfered + // WHEN ACTION_MOVE is received after + final TouchProcessorResult processorResultUnchanged = + new TouchProcessorResult.ProcessedTouch( + InteractionEvent.UNCHANGED, 1 /* pointerId */, touchData); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultUnchanged); + event.setAction(ACTION_MOVE); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); + mBiometricExecutor.runAllReady(); + event.recycle(); + + // THEN only pilfer once on the initial down verify(mInputManager).pilferPointers(any()); } 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 000000000000..0df4fbf86d98 --- /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 4836af635aed..ec7ce634fd78 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 androidx.test.filters.SmallTest 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.Mock 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) } + runCurrent() - val values = mutableListOf<Boolean>() - val job = launch { repository.isShowing.toList(values) } - assertThat(values).containsExactly(true) + assertThat(values).containsExactly(true) - withArgCaptor<AuthController.Callback> { - verify(authController).addCallback(capture()) + withArgCaptor<AuthController.Callback> { + verify(authController).addCallback(capture()) - value.onBiometricPromptShown() - assertThat(values).containsExactly(true, true) + value.onBiometricPromptShown() + runCurrent() + assertThat(values).containsExactly(true, true) - value.onBiometricPromptDismissed() - assertThat(values).containsExactly(true, true, false).inOrder() + value.onBiometricPromptDismissed() + runCurrent() + assertThat(values).containsExactly(true, true, false).inOrder() - job.cancel() - verify(authController).removeCallback(eq(value)) + 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) + + for (case in listOf(true, false)) { + repository.setPrompt( + PromptInfo().apply { isConfirmationRequested = case }, + USER_ID, + CHALLENGE, + PromptKind.Biometric() + ) + + assertThat(isConfirmationRequired).isEqualTo(case) + } + } - repository.setPrompt(promptInfo, uid, challenge, kind) + @Test + fun isConfirmationRequired_whenForced() = + testScope.runTest { + faceSettings.setUserSettings(USER_ID, alwaysRequireConfirmationInApps = true) + val isConfirmationRequired by collectLastValue(repository.isConfirmationRequired) + + for (case in listOf(true, false)) { + repository.setPrompt( + PromptInfo().apply { isConfirmationRequested = case }, + USER_ID, + CHALLENGE, + PromptKind.Biometric() + ) + + assertThat(isConfirmationRequired).isTrue() + } + } - assertThat(repository.kind.value).isEqualTo(kind) - assertThat(repository.userId.value).isEqualTo(uid) - assertThat(repository.challenge.value).isEqualTo(challenge) - assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo) + @Test + fun setsAndUnsetsPrompt() = + testScope.runTest { + val kind = PromptKind.Pin + val promptInfo = PromptInfo() - repository.unsetPrompt() + repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind) - assertThat(repository.promptInfo.value).isNull() - assertThat(repository.userId.value).isNull() - assertThat(repository.challenge.value).isNull() - } + 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 a62ea3b77fc2..81cbaeab2a32 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 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { 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 689bb0023675..fff1b81db628 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 @@ class PromptAuthStateTest : SysuiTestCase() { with(PromptAuthState(isAuthenticated = false)) { assertThat(isNotAuthenticated).isTrue() assertThat(isAuthenticatedAndConfirmed).isFalse() + assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse() assertThat(isAuthenticatedByFace).isFalse() assertThat(isAuthenticatedByFingerprint).isFalse() } @@ -43,6 +44,7 @@ class PromptAuthStateTest : SysuiTestCase() { with(PromptAuthState(isAuthenticated = true)) { assertThat(isNotAuthenticated).isFalse() assertThat(isAuthenticatedAndConfirmed).isTrue() + assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse() assertThat(isAuthenticatedByFace).isFalse() assertThat(isAuthenticatedByFingerprint).isFalse() } @@ -50,10 +52,12 @@ class PromptAuthStateTest : SysuiTestCase() { 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 @@ class PromptAuthStateTest : SysuiTestCase() { ) { assertThat(isNotAuthenticated).isFalse() assertThat(isAuthenticatedAndConfirmed).isTrue() + assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse() assertThat(isAuthenticatedByFace).isTrue() assertThat(isAuthenticatedByFingerprint).isFalse() } @@ -79,6 +84,7 @@ class PromptAuthStateTest : SysuiTestCase() { ) { 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 3ba6004e4532..5b3edaba8bc0 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 @@ private fun PromptSelectorInteractor.initializePrompt( } 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 000000000000..9d9b263c5df5 --- /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 000000000000..af2706e6b287 --- /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 d270700aa856..42ec8fed0127 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 @@ class FakePromptRepository : PromptRepository { 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/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index e4a5a3e0ed00..ca15dd79adbc 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -2166,7 +2166,7 @@ public final class GameManagerService extends IGameManagerService.Stub { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { synchronized (mUidObserverLock) { - if (ActivityManager.isProcStateBackground(procState)) { + if (procState != ActivityManager.PROCESS_STATE_TOP) { disableGameMode(uid); return; } diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index 8ef2a1bd26c2..cb5e7f1be571 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_FINGERPRIN 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 @@ public final class AuthSession implements IBinder.DeathRecipient { 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 @@ public final class AuthSession implements IBinder.DeathRecipient { 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 @@ public final class AuthSession implements IBinder.DeathRecipient { } 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 @@ public final class AuthSession implements IBinder.DeathRecipient { 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 000000000000..6727fbcdec66 --- /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 000000000000..b6701da1d348 --- /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 0942d8527565..1fa97a3cb97f 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.Context; 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 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */, - getContext()); + getContext(), mBiometricSensorPrivacy); } /** @@ -1026,6 +1029,11 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { mRequestCounter = mInjector.getRequestGenerator(); mBiometricContext = injector.getBiometricContext(context); mUserManager = injector.getUserManager(context); + mBiometricSensorPrivacy = injector.getBiometricSensorPrivacy(context); try { injector.getActivityManagerService().registerUserSwitchObserver( @@ -1290,7 +1299,7 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { + 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 3813fd1971a6..e6f25cb88006 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.annotation.NonNull; 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 @@ class PreAuthInfo { 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 @@ class PreAuthInfo { 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 @@ class PreAuthInfo { checkDevicePolicyManager, requestedStrength, promptInfo.getAllowedSensorIds(), promptInfo.isIgnoreEnrollmentState(), - context); + biometricSensorPrivacy); Slog.d(TAG, "Package: " + opPackageName + " Sensor ID: " + sensor.id @@ -138,7 +141,7 @@ class PreAuthInfo { // // 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 @@ class PreAuthInfo { return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested, - promptInfo.isIgnoreEnrollmentState(), userId, context); + promptInfo.isIgnoreEnrollmentState(), userId, context, biometricSensorPrivacy); } /** @@ -165,7 +168,7 @@ class PreAuthInfo { 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 @@ class PreAuthInfo { && !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 @@ class PreAuthInfo { } 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 @@ class PreAuthInfo { @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 @@ class PreAuthInfo { // 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 @@ class PreAuthInfo { // 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 46c77e8a82f2..aa6a0f1bb55f 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 abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement 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 05ca6e4554fb..6ac163121d8c 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 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions> } } 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 7fa4d6ce4d49..78c38089e803 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 @@ public class BiometricScheduler { 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/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java index 2e1a363bcc68..1a682a9ffefa 100644 --- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java +++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java @@ -170,6 +170,12 @@ public class ClientMonitorCallbackConverter { } } + public void onUdfpsOverlayShown() throws RemoteException { + if (mFingerprintServiceReceiver != null) { + mFingerprintServiceReceiver.onUdfpsOverlayShown(); + } + } + // Face-specific callbacks for FaceManager only /** diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 50d375c56f4a..35fc43ae5f74 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -43,7 +43,6 @@ import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; -import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; @@ -242,9 +241,6 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut vendorCode, getTargetUserId())); - if (error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL) { - BiometricNotificationUtils.showReEnrollmentNotification(getContext()); - } super.onError(error, vendorCode); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index ece35c522ec7..3d6a156d6022 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -929,17 +929,19 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override - public void onUiReady(long requestId, int sensorId) { - super.onUiReady_enforcePermission(); + public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId, + int sensorId) { + super.onUdfpsUiEvent_enforcePermission(); final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { - Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId); + Slog.w(TAG, "No matching provider for onUdfpsUiEvent, sensorId: " + sensorId); return; } - provider.onUiReady(requestId, sensorId); + provider.onUdfpsUiEvent(event, requestId, sensorId); } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index 004af2c2ad62..26701c110c39 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -130,7 +130,7 @@ public interface ServiceProvider extends void onPointerUp(long requestId, int sensorId, PointerContext pc); - void onUiReady(long requestId, int sensorId); + void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId, int sensorId); void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java index da7163a2a678..dce0175ca593 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java @@ -17,6 +17,7 @@ package com.android.server.biometrics.sensors.fingerprint; import android.hardware.biometrics.fingerprint.PointerContext; +import android.hardware.fingerprint.FingerprintManager; import com.android.server.biometrics.sensors.BaseClientMonitor; @@ -28,6 +29,6 @@ import com.android.server.biometrics.sensors.BaseClientMonitor; public interface Udfps { void onPointerDown(PointerContext pc); void onPointerUp(PointerContext pc); - void onUiReady(); + void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event); boolean isPointerDown(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java index 135eccf9026a..ec1eeb138505 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java @@ -114,6 +114,11 @@ class BiometricTestSessionImpl extends ITestSession.Stub { public void onUdfpsPointerUp(int sensorId) { } + + @Override + public void onUdfpsOverlayShown() { + + } }; BiometricTestSessionImpl(@NonNull Context context, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 2bfc2391b948..3fc36b6df92d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -27,6 +27,7 @@ import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.fingerprint.FingerprintAuthenticateOptions; +import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlay; @@ -363,9 +364,11 @@ class FingerprintAuthenticationClient } @Override - public void onUiReady() { + public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) { try { - getFreshDaemon().getSession().onUiReady(); + if (event == FingerprintManager.UDFPS_UI_READY) { + getFreshDaemon().getSession().onUiReady(); + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index c2ca78e91fe3..d35469c4655c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -265,11 +265,20 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps } @Override - public void onUiReady() { + public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) { try { - getFreshDaemon().getSession().onUiReady(); + switch (event) { + case FingerprintManager.UDFPS_UI_OVERLAY_SHOWN: + getListener().onUdfpsOverlayShown(); + break; + case FingerprintManager.UDFPS_UI_READY: + getFreshDaemon().getSession().onUiReady(); + break; + default: + Slog.w(TAG, "No matching event for onUdfpsUiEvent"); + } } catch (RemoteException e) { - Slog.e(TAG, "Unable to send UI ready", e); + Slog.e(TAG, "Unable to send onUdfpsUiEvent", e); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 58ece898a9fe..3e33ef63c7d7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -669,14 +669,15 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override - public void onUiReady(long requestId, int sensorId) { + public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId, + int sensorId) { mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches( requestId, (client) -> { if (!(client instanceof Udfps)) { - Slog.e(getTag(), "onUiReady received during client: " + client); + Slog.e(getTag(), "onUdfpsUiEvent received during client: " + client); return; } - ((Udfps) client).onUiReady(); + ((Udfps) client).onUdfpsUiEvent(event); }); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java index 86a9f7998398..c20a9eb958c4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java @@ -115,6 +115,11 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { public void onUdfpsPointerUp(int sensorId) { } + + @Override + public void onUdfpsOverlayShown() { + + } }; BiometricTestSessionImpl(@NonNull Context context, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 9e6f4e4f13f3..1cbbf89e052a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -829,13 +829,14 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } @Override - public void onUiReady(long requestId, int sensorId) { + public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId, + int sensorId) { mScheduler.getCurrentClientIfMatches(requestId, (client) -> { if (!(client instanceof Udfps)) { - Slog.w(TAG, "onUiReady received during client: " + client); + Slog.w(TAG, "onUdfpsUiEvent received during client: " + client); return; } - ((Udfps) client).onUiReady(); + ((Udfps) client).onUdfpsUiEvent(event); }); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index d22aef8b3971..2a6233824c2e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -27,6 +27,7 @@ import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.FingerprintAuthenticateOptions; +import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlay; @@ -273,7 +274,7 @@ class FingerprintAuthenticationClient } @Override - public void onUiReady() { + public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) { // Unsupported in HIDL. } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java index 362c820b9e8d..ed0a2015a461 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java @@ -25,6 +25,7 @@ import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.FingerprintAuthenticateOptions; +import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IUdfpsOverlay; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; @@ -130,7 +131,7 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> } @Override - public void onUiReady() { + public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) { // Unsupported in HIDL. } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index 78039ef99986..c2b7944eac35 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -186,7 +186,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint } @Override - public void onUiReady() { + public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) { // Unsupported in HIDL. } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index e85eee817d29..e3262cfbd30b 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -22,7 +22,6 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; @@ -280,15 +279,22 @@ public class Vpn { private static final int VPN_DEFAULT_SCORE = 101; /** - * The reset session timer for data stall. If a session has not successfully revalidated after - * the delay, the session will be torn down and restarted in an attempt to recover. Delay + * The recovery timer for data stall. If a session has not successfully revalidated after + * the delay, the session will perform MOBIKE or be restarted in an attempt to recover. Delay * counter is reset on successful validation only. * + * <p>The first {@code MOBIKE_RECOVERY_ATTEMPT} timers are used for performing MOBIKE. + * System will perform session reset for the remaining timers. * <p>If retries have exceeded the length of this array, the last entry in the array will be * used as a repeating interval. */ - private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L}; - + // TODO: use ms instead to speed up the test. + private static final long[] DATA_STALL_RECOVERY_DELAYS_SEC = + {1L, 5L, 30L, 60L, 120L, 240L, 480L, 960L}; + /** + * Maximum attempts to perform MOBIKE when the network is bad. + */ + private static final int MAX_MOBIKE_RECOVERY_ATTEMPT = 2; /** * The initial token value of IKE session. */ @@ -380,6 +386,7 @@ public class Vpn { private final INetworkManagementService mNms; private final INetd mNetd; @VisibleForTesting + @GuardedBy("this") protected VpnConfig mConfig; private final NetworkProvider mNetworkProvider; @VisibleForTesting @@ -392,7 +399,6 @@ public class Vpn { private final UserManager mUserManager; private final VpnProfileStore mVpnProfileStore; - protected boolean mDataStallSuspected = false; @VisibleForTesting VpnProfileStore getVpnProfileStore() { @@ -685,14 +691,14 @@ public class Vpn { } /** - * Get the length of time to wait before resetting the ike session when a data stall is - * suspected. + * Get the length of time to wait before perform data stall recovery when the validation + * result is bad. */ - public long getDataStallResetSessionSeconds(int count) { - if (count >= DATA_STALL_RESET_DELAYS_SEC.length) { - return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1]; + public long getValidationFailRecoverySeconds(int count) { + if (count >= DATA_STALL_RECOVERY_DELAYS_SEC.length) { + return DATA_STALL_RECOVERY_DELAYS_SEC[DATA_STALL_RECOVERY_DELAYS_SEC.length - 1]; } else { - return DATA_STALL_RESET_DELAYS_SEC[count]; + return DATA_STALL_RECOVERY_DELAYS_SEC[count]; } } @@ -1598,6 +1604,8 @@ public class Vpn { return network; } + // TODO : this is not synchronized(this) but reads from mConfig, which is dangerous + // This file makes an effort to avoid partly initializing mConfig, but this is still not great private LinkProperties makeLinkProperties() { // The design of disabling IPv6 is only enabled for IKEv2 VPN because it needs additional // logic to handle IPv6 only VPN, and the IPv6 only VPN may be restarted when its MTU @@ -1679,6 +1687,7 @@ public class Vpn { * registering a new NetworkAgent. This is not always possible if the new VPN configuration * has certain changes, in which case this method would just return {@code false}. */ + // TODO : this method is not synchronized(this) but reads from mConfig private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) { // NetworkAgentConfig cannot be updated without registering a new NetworkAgent. // Strictly speaking, bypassability is affected by lockdown and therefore it's possible @@ -2269,7 +2278,12 @@ public class Vpn { */ public synchronized VpnConfig getVpnConfig() { enforceControlPermission(); - return mConfig; + // Constructor of VpnConfig cannot take a null parameter. Return null directly if mConfig is + // null + if (mConfig == null) return null; + // mConfig is guarded by "this" and can be modified by another thread as soon as + // this method returns, so this method must return a copy. + return new VpnConfig(mConfig); } @Deprecated @@ -2315,6 +2329,7 @@ public class Vpn { } }; + @GuardedBy("this") private void cleanupVpnStateLocked() { mStatusIntent = null; resetNetworkCapabilities(); @@ -2837,9 +2852,7 @@ public class Vpn { } final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner; - mVpnRunner.exit(); - mVpnRunner = null; // LegacyVpn uses daemons that must be shut down before new ones are brought up. // The same limitation does not apply to Platform VPNs. @@ -3044,7 +3057,6 @@ public class Vpn { @Nullable private IkeSessionWrapper mSession; @Nullable private IkeSessionConnectionInfo mIkeConnectionInfo; - @Nullable private VpnConnectivityDiagnosticsCallback mDiagnosticsCallback; // mMobikeEnabled can only be updated after IKE AUTH is finished. private boolean mMobikeEnabled = false; @@ -3055,7 +3067,7 @@ public class Vpn { * <p>This variable controls the retry delay, and is reset when the VPN pass network * validation. */ - private int mDataStallRetryCount = 0; + private int mValidationFailRetryCount = 0; /** * The number of attempts since the last successful connection. @@ -3084,6 +3096,7 @@ public class Vpn { } }; + // GuardedBy("Vpn.this") (annotation can't be applied to constructor) IkeV2VpnRunner( @NonNull Ikev2VpnProfile profile, @NonNull ScheduledThreadPoolExecutor executor) { super(TAG); @@ -3136,15 +3149,6 @@ public class Vpn { mConnectivityManager.registerSystemDefaultNetworkCallback(mNetworkCallback, new Handler(mLooper)); } - - // DiagnosticsCallback may return more than one alive VPNs, but VPN will filter based on - // Network object. - final NetworkRequest diagRequest = new NetworkRequest.Builder() - .addTransportType(TRANSPORT_VPN) - .removeCapability(NET_CAPABILITY_NOT_VPN).build(); - mDiagnosticsCallback = new VpnConnectivityDiagnosticsCallback(); - mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback( - diagRequest, mExecutor, mDiagnosticsCallback); } private boolean isActiveNetwork(@Nullable Network network) { @@ -3710,11 +3714,14 @@ public class Vpn { } public void updateVpnTransportInfoAndNetCap(int keepaliveDelaySec) { - final VpnTransportInfo info = new VpnTransportInfo( - getActiveVpnType(), - mConfig.session, - mConfig.allowBypass && !mLockdown, - areLongLivedTcpConnectionsExpensive(keepaliveDelaySec)); + final VpnTransportInfo info; + synchronized (Vpn.this) { + info = new VpnTransportInfo( + getActiveVpnType(), + mConfig.session, + mConfig.allowBypass && !mLockdown, + areLongLivedTcpConnectionsExpensive(keepaliveDelaySec)); + } final boolean ncUpdateRequired = !info.equals(mNetworkCapabilities.getTransportInfo()); if (ncUpdateRequired) { mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities) @@ -3875,39 +3882,12 @@ public class Vpn { } } - class VpnConnectivityDiagnosticsCallback - extends ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { - // The callback runs in the executor thread. - @Override - public void onDataStallSuspected( - ConnectivityDiagnosticsManager.DataStallReport report) { - synchronized (Vpn.this) { - // Ignore stale runner. - if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return; - - // Handle the report only for current VPN network. If data stall is already - // reported, ignoring the other reports. It means that the stall is not - // recovered by MOBIKE and should be on the way to reset the ike session. - if (mNetworkAgent != null - && mNetworkAgent.getNetwork().equals(report.getNetwork()) - && !mDataStallSuspected) { - Log.d(TAG, "Data stall suspected"); - - // Trigger MOBIKE. - maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork); - mDataStallSuspected = true; - } - } - } - } - public void onValidationStatus(int status) { mEventChanges.log("[Validation] validation status " + status); if (status == NetworkAgent.VALIDATION_STATUS_VALID) { // No data stall now. Reset it. mExecutor.execute(() -> { - mDataStallSuspected = false; - mDataStallRetryCount = 0; + mValidationFailRetryCount = 0; if (mScheduledHandleDataStallFuture != null) { Log.d(TAG, "Recovered from stall. Cancel pending reset action."); mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */); @@ -3918,8 +3898,21 @@ public class Vpn { // Skip other invalid status if the scheduled recovery exists. if (mScheduledHandleDataStallFuture != null) return; + if (mValidationFailRetryCount < MAX_MOBIKE_RECOVERY_ATTEMPT) { + Log.d(TAG, "Validation failed"); + + // Trigger MOBIKE to recover first. + mExecutor.schedule(() -> { + maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork); + }, mDeps.getValidationFailRecoverySeconds(mValidationFailRetryCount++), + TimeUnit.SECONDS); + return; + } + + // Data stall is not recovered by MOBIKE. Try to reset session to recover it. mScheduledHandleDataStallFuture = mExecutor.schedule(() -> { - if (mDataStallSuspected) { + // Only perform the recovery when the network is still bad. + if (mValidationFailRetryCount > 0) { Log.d(TAG, "Reset session to recover stalled network"); // This will reset old state if it exists. startIkeSession(mActiveNetwork); @@ -3928,7 +3921,9 @@ public class Vpn { // Reset mScheduledHandleDataStallFuture since it's already run on executor // thread. mScheduledHandleDataStallFuture = null; - }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS); + // TODO: compute the delay based on the last recovery timestamp + }, mDeps.getValidationFailRecoverySeconds(mValidationFailRetryCount++), + TimeUnit.SECONDS); } } @@ -4220,7 +4215,7 @@ public class Vpn { * consistency of the Ikev2VpnRunner fields. */ private void disconnectVpnRunner() { - mEventChanges.log("[VPNRunner] Disconnect runner, underlying network" + mActiveNetwork); + mEventChanges.log("[VPNRunner] Disconnect runner, underlying net " + mActiveNetwork); mActiveNetwork = null; mUnderlyingNetworkCapabilities = null; mUnderlyingLinkProperties = null; @@ -4231,8 +4226,6 @@ public class Vpn { mCarrierConfigManager.unregisterCarrierConfigChangeListener( mCarrierConfigChangeListener); mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); - mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback( - mDiagnosticsCallback); clearVpnNetworkPreference(mSessionKey); mExecutor.shutdown(); @@ -4293,6 +4286,7 @@ public class Vpn { } }; + // GuardedBy("Vpn.this") (annotation can't be applied to constructor) LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) { super(TAG); if (racoon == null && mtpd == null) { @@ -4500,46 +4494,46 @@ public class Vpn { } // Set the interface and the addresses in the config. - mConfig.interfaze = parameters[0].trim(); + synchronized (Vpn.this) { + mConfig.interfaze = parameters[0].trim(); - mConfig.addLegacyAddresses(parameters[1]); - // Set the routes if they are not set in the config. - if (mConfig.routes == null || mConfig.routes.isEmpty()) { - mConfig.addLegacyRoutes(parameters[2]); - } + mConfig.addLegacyAddresses(parameters[1]); + // Set the routes if they are not set in the config. + if (mConfig.routes == null || mConfig.routes.isEmpty()) { + mConfig.addLegacyRoutes(parameters[2]); + } - // Set the DNS servers if they are not set in the config. - if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) { - String dnsServers = parameters[3].trim(); - if (!dnsServers.isEmpty()) { - mConfig.dnsServers = Arrays.asList(dnsServers.split(" ")); + // Set the DNS servers if they are not set in the config. + if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) { + String dnsServers = parameters[3].trim(); + if (!dnsServers.isEmpty()) { + mConfig.dnsServers = Arrays.asList(dnsServers.split(" ")); + } } - } - // Set the search domains if they are not set in the config. - if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) { - String searchDomains = parameters[4].trim(); - if (!searchDomains.isEmpty()) { - mConfig.searchDomains = Arrays.asList(searchDomains.split(" ")); + // Set the search domains if they are not set in the config. + if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) { + String searchDomains = parameters[4].trim(); + if (!searchDomains.isEmpty()) { + mConfig.searchDomains = Arrays.asList(searchDomains.split(" ")); + } } - } - // Add a throw route for the VPN server endpoint, if one was specified. - if (endpointAddress instanceof Inet4Address) { - mConfig.routes.add(new RouteInfo( - new IpPrefix(endpointAddress, 32), null /*gateway*/, - null /*iface*/, RTN_THROW)); - } else if (endpointAddress instanceof Inet6Address) { - mConfig.routes.add(new RouteInfo( - new IpPrefix(endpointAddress, 128), null /*gateway*/, - null /*iface*/, RTN_THROW)); - } else { - Log.e(TAG, "Unknown IP address family for VPN endpoint: " - + endpointAddress); - } + // Add a throw route for the VPN server endpoint, if one was specified. + if (endpointAddress instanceof Inet4Address) { + mConfig.routes.add(new RouteInfo( + new IpPrefix(endpointAddress, 32), null /*gateway*/, + null /*iface*/, RTN_THROW)); + } else if (endpointAddress instanceof Inet6Address) { + mConfig.routes.add(new RouteInfo( + new IpPrefix(endpointAddress, 128), null /*gateway*/, + null /*iface*/, RTN_THROW)); + } else { + Log.e(TAG, "Unknown IP address family for VPN endpoint: " + + endpointAddress); + } - // Here is the last step and it must be done synchronously. - synchronized (Vpn.this) { + // Here is the last step and it must be done synchronously. // Set the start time mConfig.startTime = SystemClock.elapsedRealtime(); @@ -4773,25 +4767,26 @@ public class Vpn { try { // Build basic config - mConfig = new VpnConfig(); + final VpnConfig config = new VpnConfig(); if (VpnConfig.LEGACY_VPN.equals(packageName)) { - mConfig.legacy = true; - mConfig.session = profile.name; - mConfig.user = profile.key; + config.legacy = true; + config.session = profile.name; + config.user = profile.key; // TODO: Add support for configuring meteredness via Settings. Until then, use a // safe default. - mConfig.isMetered = true; + config.isMetered = true; } else { - mConfig.user = packageName; - mConfig.isMetered = profile.isMetered; + config.user = packageName; + config.isMetered = profile.isMetered; } - mConfig.startTime = SystemClock.elapsedRealtime(); - mConfig.proxyInfo = profile.proxy; - mConfig.requiresInternetValidation = profile.requiresInternetValidation; - mConfig.excludeLocalRoutes = profile.excludeLocalRoutes; - mConfig.allowBypass = profile.isBypassable; - mConfig.disallowedApplications = getAppExclusionList(mPackage); + config.startTime = SystemClock.elapsedRealtime(); + config.proxyInfo = profile.proxy; + config.requiresInternetValidation = profile.requiresInternetValidation; + config.excludeLocalRoutes = profile.excludeLocalRoutes; + config.allowBypass = profile.isBypassable; + config.disallowedApplications = getAppExclusionList(mPackage); + mConfig = config; switch (profile.type) { case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: @@ -4805,6 +4800,7 @@ public class Vpn { mVpnRunner.start(); break; default: + mConfig = null; updateState(DetailedState.FAILED, "Invalid platform VPN type"); Log.d(TAG, "Unknown VPN profile type: " + profile.type); break; @@ -5216,7 +5212,7 @@ public class Vpn { pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled")); pw.println("Profile: " + runner.mProfile); pw.println("Token: " + runner.mCurrentToken); - if (mDataStallSuspected) pw.println("Data stall suspected"); + pw.println("Validation failed retry count:" + runner.mValidationFailRetryCount); if (runner.mScheduledHandleDataStallFuture != null) { pw.println("Reset session scheduled"); } diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 75709fbb365a..d647757442e0 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -223,11 +223,11 @@ public class AutomaticBrightnessController { private final ShortTermModel mShortTermModel; private final ShortTermModel mPausedShortTermModel; - // Controls High Brightness Mode. - private HighBrightnessModeController mHbmController; + // Controls Brightness range (including High Brightness Mode). + private final BrightnessRangeController mBrightnessRangeController; // Throttles (caps) maximum allowed brightness - private BrightnessThrottler mBrightnessThrottler; + private final BrightnessThrottler mBrightnessThrottler; private boolean mIsBrightnessThrottled; // Context-sensitive brightness configurations require keeping track of the foreground app's @@ -257,7 +257,8 @@ public class AutomaticBrightnessController { HysteresisLevels screenBrightnessThresholds, HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, - HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler, + BrightnessRangeController brightnessModeController, + BrightnessThrottler brightnessThrottler, BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux, float userBrightness) { this(new Injector(), callbacks, looper, sensorManager, lightSensor, @@ -267,7 +268,7 @@ public class AutomaticBrightnessController { darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context, - hbmController, brightnessThrottler, idleModeBrightnessMapper, + brightnessModeController, brightnessThrottler, idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong, userLux, userBrightness ); } @@ -283,7 +284,8 @@ public class AutomaticBrightnessController { HysteresisLevels screenBrightnessThresholds, HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, - HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler, + BrightnessRangeController brightnessModeController, + BrightnessThrottler brightnessThrottler, BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux, float userBrightness) { mInjector = injector; @@ -326,7 +328,7 @@ public class AutomaticBrightnessController { mPendingForegroundAppPackageName = null; mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; - mHbmController = hbmController; + mBrightnessRangeController = brightnessModeController; mBrightnessThrottler = brightnessThrottler; mInteractiveModeBrightnessMapper = interactiveModeBrightnessMapper; mIdleModeBrightnessMapper = idleModeBrightnessMapper; @@ -607,10 +609,11 @@ public class AutomaticBrightnessController { pw.println(); pw.println(" mInteractiveMapper="); - mInteractiveModeBrightnessMapper.dump(pw, mHbmController.getNormalBrightnessMax()); + mInteractiveModeBrightnessMapper.dump(pw, + mBrightnessRangeController.getNormalBrightnessMax()); if (mIdleModeBrightnessMapper != null) { pw.println(" mIdleMapper="); - mIdleModeBrightnessMapper.dump(pw, mHbmController.getNormalBrightnessMax()); + mIdleModeBrightnessMapper.dump(pw, mBrightnessRangeController.getNormalBrightnessMax()); } pw.println(); @@ -736,7 +739,7 @@ public class AutomaticBrightnessController { mAmbientDarkeningThreshold = mAmbientBrightnessThresholds.getDarkeningThreshold(lux); } - mHbmController.onAmbientLuxChange(mAmbientLux); + mBrightnessRangeController.onAmbientLuxChange(mAmbientLux); // If the short term model was invalidated and the change is drastic enough, reset it. @@ -976,9 +979,9 @@ public class AutomaticBrightnessController { // Clamps values with float range [0.0-1.0] private float clampScreenBrightness(float value) { - final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(), + final float minBrightness = Math.min(mBrightnessRangeController.getCurrentBrightnessMin(), mBrightnessThrottler.getBrightnessCap()); - final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(), + final float maxBrightness = Math.min(mBrightnessRangeController.getCurrentBrightnessMax(), mBrightnessThrottler.getBrightnessCap()); return MathUtils.constrain(value, minBrightness, maxBrightness); } diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java new file mode 100644 index 000000000000..47cde1517450 --- /dev/null +++ b/services/core/java/com/android/server/display/BrightnessRangeController.java @@ -0,0 +1,126 @@ +/* + * 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.display; + +import android.hardware.display.BrightnessInfo; +import android.os.IBinder; + +import java.io.PrintWriter; +import java.util.function.BooleanSupplier; + +class BrightnessRangeController { + + private static final boolean NBM_FEATURE_FLAG = false; + + private final HighBrightnessModeController mHbmController; + private final NormalBrightnessModeController mNormalBrightnessModeController = + new NormalBrightnessModeController(); + + private final Runnable mModeChangeCallback; + + BrightnessRangeController(HighBrightnessModeController hbmController, + Runnable modeChangeCallback) { + mHbmController = hbmController; + mModeChangeCallback = modeChangeCallback; + } + + + void dump(PrintWriter pw) { + mHbmController.dump(pw); + } + + void onAmbientLuxChange(float ambientLux) { + applyChanges( + () -> mNormalBrightnessModeController.onAmbientLuxChange(ambientLux), + () -> mHbmController.onAmbientLuxChange(ambientLux) + ); + } + + float getNormalBrightnessMax() { + return mHbmController.getNormalBrightnessMax(); + } + + void loadFromConfig(HighBrightnessModeMetadata hbmMetadata, IBinder token, + DisplayDeviceInfo info, DisplayDeviceConfig displayDeviceConfig) { + applyChanges( + () -> mNormalBrightnessModeController.resetNbmData( + displayDeviceConfig.getLuxThrottlingData()), + () -> { + mHbmController.setHighBrightnessModeMetadata(hbmMetadata); + mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId, + displayDeviceConfig.getHighBrightnessModeData(), + displayDeviceConfig::getHdrBrightnessFromSdr); + } + ); + } + + void stop() { + mHbmController.stop(); + } + + void setAutoBrightnessEnabled(int state) { + applyChanges( + () -> mNormalBrightnessModeController.setAutoBrightnessState(state), + () -> mHbmController.setAutoBrightnessEnabled(state) + ); + } + + void onBrightnessChanged(float brightness, float unthrottledBrightness, + @BrightnessInfo.BrightnessMaxReason int throttlingReason) { + mHbmController.onBrightnessChanged(brightness, unthrottledBrightness, throttlingReason); + } + + float getCurrentBrightnessMin() { + return mHbmController.getCurrentBrightnessMin(); + } + + + float getCurrentBrightnessMax() { + if (NBM_FEATURE_FLAG && mHbmController.getHighBrightnessMode() + == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) { + return Math.min(mHbmController.getCurrentBrightnessMax(), + mNormalBrightnessModeController.getCurrentBrightnessMax()); + } + return mHbmController.getCurrentBrightnessMax(); + } + + int getHighBrightnessMode() { + return mHbmController.getHighBrightnessMode(); + } + + float getHdrBrightnessValue() { + return mHbmController.getHdrBrightnessValue(); + } + + float getTransitionPoint() { + return mHbmController.getTransitionPoint(); + } + + private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) { + if (NBM_FEATURE_FLAG) { + boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean(); + hbmChangesFunc.run(); + // if nbm transition changed - trigger callback + // HighBrightnessModeController handles sending changes itself + if (nbmTransitionChanged) { + mModeChangeCallback.run(); + } + } else { + hbmChangesFunc.run(); + } + } +} diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 7a797dd2250c..7ccfb448cf61 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -41,6 +41,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.server.display.config.AutoBrightness; import com.android.server.display.config.BlockingZoneConfig; +import com.android.server.display.config.BrightnessLimitMap; import com.android.server.display.config.BrightnessThresholds; import com.android.server.display.config.BrightnessThrottlingMap; import com.android.server.display.config.BrightnessThrottlingPoint; @@ -51,8 +52,11 @@ import com.android.server.display.config.DisplayQuirks; import com.android.server.display.config.HbmTiming; import com.android.server.display.config.HighBrightnessMode; import com.android.server.display.config.IntegerArray; +import com.android.server.display.config.LuxThrottling; import com.android.server.display.config.NitsMap; +import com.android.server.display.config.NonNegativeFloatToFloatPoint; import com.android.server.display.config.Point; +import com.android.server.display.config.PredefinedBrightnessLimitNames; import com.android.server.display.config.RefreshRateConfigs; import com.android.server.display.config.RefreshRateRange; import com.android.server.display.config.RefreshRateThrottlingMap; @@ -219,6 +223,22 @@ import javax.xml.datatype.DatatypeConfigurationException; * <allowInLowPowerMode>false</allowInLowPowerMode> * </highBrightnessMode> * + * <luxThrottling> + * <brightnessLimitMap> + * <type>default</type> + * <map> + * <point> + * <first>5000</first> + * <second>0.3</second> + * </point> + * <point> + * <first>5000</first> + * <second>0.3</second> + * </point> + * </map> + * </brightnessPeakMap> + * </luxThrottling> + * * <quirks> * <quirk>canSetBrightnessViaHwc</quirk> * </quirks> @@ -693,6 +713,9 @@ public class DisplayDeviceConfig { private final Map<String, SparseArray<SurfaceControl.RefreshRateRange>> mRefreshRateThrottlingMap = new HashMap<>(); + private final Map<BrightnessLimitMapType, Map<Float, Float>> + mLuxThrottlingData = new HashMap<>(); + @Nullable private HostUsiVersion mHostUsiVersion; @@ -1344,6 +1367,11 @@ public class DisplayDeviceConfig { return hbmData; } + @NonNull + public Map<BrightnessLimitMapType, Map<Float, Float>> getLuxThrottlingData() { + return mLuxThrottlingData; + } + public List<RefreshRateLimitation> getRefreshRateLimitations() { return mRefreshRateLimitations; } @@ -1530,6 +1558,7 @@ public class DisplayDeviceConfig { + ", mBrightnessDefault=" + mBrightnessDefault + ", mQuirks=" + mQuirks + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled + + ", mLuxThrottlingData=" + mLuxThrottlingData + ", mHbmData=" + mHbmData + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline + ", mThermalBrightnessThrottlingDataMapByThrottlingId=" @@ -1676,6 +1705,7 @@ public class DisplayDeviceConfig { loadBrightnessMap(config); loadThermalThrottlingConfig(config); loadHighBrightnessModeData(config); + loadLuxThrottling(config); loadQuirks(config); loadBrightnessRamps(config); loadAmbientLightSensorFromDdc(config); @@ -2428,6 +2458,54 @@ public class DisplayDeviceConfig { } } + private void loadLuxThrottling(DisplayConfiguration config) { + LuxThrottling cfg = config.getLuxThrottling(); + if (cfg != null) { + HighBrightnessMode hbm = config.getHighBrightnessMode(); + float hbmTransitionPoint = hbm != null ? hbm.getTransitionPoint_all().floatValue() + : PowerManager.BRIGHTNESS_MAX; + List<BrightnessLimitMap> limitMaps = cfg.getBrightnessLimitMap(); + for (BrightnessLimitMap map : limitMaps) { + PredefinedBrightnessLimitNames type = map.getType(); + BrightnessLimitMapType mappedType = BrightnessLimitMapType.convert(type); + if (mappedType == null) { + Slog.wtf(TAG, "Invalid NBM config: unsupported map type=" + type); + continue; + } + if (mLuxThrottlingData.containsKey(mappedType)) { + Slog.wtf(TAG, "Invalid NBM config: duplicate map type=" + mappedType); + continue; + } + Map<Float, Float> luxToTransitionPointMap = new HashMap<>(); + + List<NonNegativeFloatToFloatPoint> points = map.getMap().getPoint(); + for (NonNegativeFloatToFloatPoint point : points) { + float lux = point.getFirst().floatValue(); + float maxBrightness = point.getSecond().floatValue(); + if (maxBrightness > hbmTransitionPoint) { + Slog.wtf(TAG, + "Invalid NBM config: maxBrightness is greater than hbm" + + ".transitionPoint. type=" + + type + "; lux=" + lux + "; maxBrightness=" + + maxBrightness); + continue; + } + if (luxToTransitionPointMap.containsKey(lux)) { + Slog.wtf(TAG, + "Invalid NBM config: duplicate lux key. type=" + type + "; lux=" + + lux); + continue; + } + luxToTransitionPointMap.put(lux, + mBacklightToBrightnessSpline.interpolate(maxBrightness)); + } + if (!luxToTransitionPointMap.isEmpty()) { + mLuxThrottlingData.put(mappedType, luxToTransitionPointMap); + } + } + } + } + private void loadBrightnessRamps(DisplayConfiguration config) { // Priority 1: Value in the display device config (float) // Priority 2: Value in the config.xml (int) @@ -3155,4 +3233,19 @@ public class DisplayDeviceConfig { } } } + + public enum BrightnessLimitMapType { + DEFAULT, ADAPTIVE; + + @Nullable + private static BrightnessLimitMapType convert(PredefinedBrightnessLimitNames type) { + switch (type) { + case _default: + return DEFAULT; + case adaptive: + return ADAPTIVE; + } + return null; + } + } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 9d31572c7d76..f6e074f41743 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -445,7 +445,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private final ColorDisplayServiceInternal mCdsi; private float[] mNitsRange; - private final HighBrightnessModeController mHbmController; + private final BrightnessRangeController mBrightnessRangeController; private final HighBrightnessModeMetadata mHighBrightnessModeMetadata; private final BrightnessThrottler mBrightnessThrottler; @@ -654,8 +654,19 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call loadBrightnessRampRates(); mSkipScreenOnBrightnessRamp = resources.getBoolean( com.android.internal.R.bool.config_skipScreenOnBrightnessRamp); + Runnable modeChangeCallback = () -> { + sendUpdatePowerState(); + postBrightnessChangeRunnable(); + // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.update(); + } + }; - mHbmController = createHbmControllerLocked(); + HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback); + + mBrightnessRangeController = new BrightnessRangeController(hbmController, + modeChangeCallback); mBrightnessThrottler = createBrightnessThrottlerLocked(); @@ -802,7 +813,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call @Override public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) { - mHbmController.onAmbientLuxChange(ambientLux); + mBrightnessRangeController.onAmbientLuxChange(ambientLux); if (nits < 0) { mBrightnessToFollow = leadDisplayBrightness; } else { @@ -1039,17 +1050,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessRampIncreaseMaxTimeMillis, mBrightnessRampDecreaseMaxTimeMillis); } - mHbmController.setHighBrightnessModeMetadata(hbmMetadata); - mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId, - mDisplayDeviceConfig.getHighBrightnessModeData(), - new HighBrightnessModeController.HdrBrightnessDeviceConfig() { - @Override - public float getHdrBrightnessFromSdr( - float sdrBrightness, float maxDesiredHdrSdrRatio) { - return mDisplayDeviceConfig.getHdrBrightnessFromSdr( - sdrBrightness, maxDesiredHdrSdrRatio); - } - }); + mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig); mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig( mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(), mThermalBrightnessThrottlingDataId, mUniqueDisplayId); @@ -1264,7 +1265,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext, - mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper, + mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper, mDisplayDeviceConfig.getAmbientHorizonShort(), mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness); @@ -1364,7 +1365,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call /** Clean up all resources that are accessed via the {@link #mHandler} thread. */ private void cleanupHandlerThreadAfterStop() { setProximitySensorEnabled(false); - mHbmController.stop(); + mBrightnessRangeController.stop(); mBrightnessThrottler.stop(); mHandler.removeCallbacksAndMessages(null); @@ -1647,7 +1648,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mShouldResetShortTermModel); mShouldResetShortTermModel = false; } - mHbmController.setAutoBrightnessEnabled(mUseAutoBrightness + mBrightnessRangeController.setAutoBrightnessEnabled(mUseAutoBrightness ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED); @@ -1820,7 +1821,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // here instead of having HbmController listen to the brightness setting because certain // brightness sources (such as an app override) are not saved to the setting, but should be // reflected in HBM calculations. - mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState, + mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState, mBrightnessThrottler.getBrightnessMaxReason()); // Animate the screen brightness when the screen is on or dozing. @@ -1874,13 +1875,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call float sdrAnimateValue = animateValue; // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be // done in HighBrightnessModeController. - if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR + if (mBrightnessRangeController.getHighBrightnessMode() + == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR && (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0 && (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_LOW_POWER) == 0) { // We want to scale HDR brightness level with the SDR level, we also need to restore // SDR brightness immediately when entering dim or low power mode. - animateValue = mHbmController.getHdrBrightnessValue(); + animateValue = mBrightnessRangeController.getHdrBrightnessValue(); } final float currentBrightness = mPowerState.getScreenBrightness(); @@ -1942,8 +1944,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mTempBrightnessEvent.setBrightness(brightnessState); mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId); mTempBrightnessEvent.setReason(mBrightnessReason); - mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax()); - mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode()); + mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax()); + mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode()); mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags() | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0) | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0)); @@ -2104,9 +2106,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) { synchronized (mCachedBrightnessInfo) { - final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(), + final float minBrightness = Math.min( + mBrightnessRangeController.getCurrentBrightnessMin(), mBrightnessThrottler.getBrightnessCap()); - final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(), + final float maxBrightness = Math.min( + mBrightnessRangeController.getCurrentBrightnessMax(), mBrightnessThrottler.getBrightnessCap()); boolean changed = false; @@ -2124,10 +2128,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call maxBrightness); changed |= mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode, - mHbmController.getHighBrightnessMode()); + mBrightnessRangeController.getHighBrightnessMode()); changed |= mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint, - mHbmController.getTransitionPoint()); + mBrightnessRangeController.getTransitionPoint()); changed |= mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason, mBrightnessThrottler.getBrightnessMaxReason()); @@ -2137,10 +2141,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } void postBrightnessChangeRunnable() { - mHandler.post(mOnBrightnessChangeRunnable); + if (!mHandler.hasCallbacks(mOnBrightnessChangeRunnable)) { + mHandler.post(mOnBrightnessChangeRunnable); + } } - private HighBrightnessModeController createHbmControllerLocked() { + private HighBrightnessModeController createHbmControllerLocked( + Runnable modeChangeCallback) { final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); final IBinder displayToken = @@ -2159,15 +2166,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return mDisplayDeviceConfig.getHdrBrightnessFromSdr( sdrBrightness, maxDesiredHdrSdrRatio); } - }, - () -> { - sendUpdatePowerState(); - postBrightnessChangeRunnable(); - // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. - if (mAutomaticBrightnessController != null) { - mAutomaticBrightnessController.update(); - } - }, mHighBrightnessModeMetadata, mContext); + }, modeChangeCallback, mHighBrightnessModeMetadata, mContext); } private BrightnessThrottler createBrightnessThrottlerLocked() { @@ -2328,8 +2327,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (Float.isNaN(value)) { value = PowerManager.BRIGHTNESS_MIN; } - return MathUtils.constrain(value, - mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax()); + return MathUtils.constrain(value, mBrightnessRangeController.getCurrentBrightnessMin(), + mBrightnessRangeController.getCurrentBrightnessMax()); } // Checks whether the brightness is within the valid brightness range, not including off. @@ -3003,8 +3002,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mScreenOffBrightnessSensorController.dump(pw); } - if (mHbmController != null) { - mHbmController.dump(pw); + if (mBrightnessRangeController != null) { + mBrightnessRangeController.dump(pw); } if (mBrightnessThrottler != null) { @@ -3471,7 +3470,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call HysteresisLevels screenBrightnessThresholds, HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, - HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler, + BrightnessRangeController brightnessRangeController, + BrightnessThrottler brightnessThrottler, BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux, float userBrightness) { return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor, @@ -3480,9 +3480,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call brighteningLightDebounceConfig, darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, - screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler, - idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong, - userLux, userBrightness); + screenBrightnessThresholdsIdle, context, brightnessRangeController, + brightnessThrottler, idleModeBrightnessMapper, ambientLightHorizonShort, + ambientLightHorizonLong, userLux, userBrightness); } BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources, diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 41e4671df1a7..2e8c3420c317 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -376,8 +376,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private final ColorDisplayServiceInternal mCdsi; private float[] mNitsRange; - private final HighBrightnessModeController mHbmController; - private final HighBrightnessModeMetadata mHighBrightnessModeMetadata; + private final BrightnessRangeController mBrightnessRangeController; private final BrightnessThrottler mBrightnessThrottler; @@ -489,7 +488,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController( mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(), () -> updatePowerState(), mDisplayId, mSensorManager); - mHighBrightnessModeMetadata = hbmMetadata; mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController); mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId); mTag = "DisplayPowerController2[" + mDisplayId + "]"; @@ -532,9 +530,22 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mSkipScreenOnBrightnessRamp = resources.getBoolean( R.bool.config_skipScreenOnBrightnessRamp); - mHbmController = createHbmControllerLocked(); + Runnable modeChangeCallback = () -> { + sendUpdatePowerState(); + postBrightnessChangeRunnable(); + // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.update(); + } + }; + HighBrightnessModeController hbmController = createHbmControllerLocked(hbmMetadata, + modeChangeCallback); mBrightnessThrottler = createBrightnessThrottlerLocked(); + + mBrightnessRangeController = new BrightnessRangeController(hbmController, + modeChangeCallback); + mDisplayBrightnessController = new DisplayBrightnessController(context, null, mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault, @@ -848,17 +859,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mBrightnessRampIncreaseMaxTimeMillis, mBrightnessRampDecreaseMaxTimeMillis); } - mHbmController.setHighBrightnessModeMetadata(hbmMetadata); - mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId, - mDisplayDeviceConfig.getHighBrightnessModeData(), - new HighBrightnessModeController.HdrBrightnessDeviceConfig() { - @Override - public float getHdrBrightnessFromSdr( - float sdrBrightness, float maxDesiredHdrSdrRatio) { - return mDisplayDeviceConfig.getHdrBrightnessFromSdr( - sdrBrightness, maxDesiredHdrSdrRatio); - } - }); + + mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig); mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig( mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(), mThermalBrightnessThrottlingDataId, mUniqueDisplayId); @@ -1076,7 +1078,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext, - mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper, + mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper, mDisplayDeviceConfig.getAmbientHorizonShort(), mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness); mDisplayBrightnessController.setAutomaticBrightnessController( @@ -1180,7 +1182,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal /** Clean up all resources that are accessed via the {@link #mHandler} thread. */ private void cleanupHandlerThreadAfterStop() { mDisplayPowerProximityStateController.cleanup(); - mHbmController.stop(); + mBrightnessRangeController.stop(); mBrightnessThrottler.stop(); mHandler.removeCallbacksAndMessages(null); @@ -1295,7 +1297,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged() || userSetBrightnessChanged); - mHbmController.setAutoBrightnessEnabled(mAutomaticBrightnessStrategy + mBrightnessRangeController.setAutoBrightnessEnabled(mAutomaticBrightnessStrategy .shouldUseAutoBrightness() ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED); @@ -1452,7 +1454,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // here instead of having HbmController listen to the brightness setting because certain // brightness sources (such as an app override) are not saved to the setting, but should be // reflected in HBM calculations. - mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState, + mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState, mBrightnessThrottler.getBrightnessMaxReason()); // Animate the screen brightness when the screen is on or dozing. @@ -1509,13 +1511,14 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal float sdrAnimateValue = animateValue; // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be // done in HighBrightnessModeController. - if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR + if (mBrightnessRangeController.getHighBrightnessMode() + == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR && (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0 && (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_LOW_POWER) == 0) { // We want to scale HDR brightness level with the SDR level, we also need to restore // SDR brightness immediately when entering dim or low power mode. - animateValue = mHbmController.getHdrBrightnessValue(); + animateValue = mBrightnessRangeController.getHdrBrightnessValue(); } final float currentBrightness = mPowerState.getScreenBrightness(); @@ -1579,8 +1582,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mTempBrightnessEvent.setBrightness(brightnessState); mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId); mTempBrightnessEvent.setReason(mBrightnessReason); - mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax()); - mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode()); + mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax()); + mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode()); mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags() | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0) | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0)); @@ -1750,9 +1753,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) { synchronized (mCachedBrightnessInfo) { - final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(), + final float minBrightness = Math.min( + mBrightnessRangeController.getCurrentBrightnessMin(), mBrightnessThrottler.getBrightnessCap()); - final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(), + final float maxBrightness = Math.min( + mBrightnessRangeController.getCurrentBrightnessMax(), mBrightnessThrottler.getBrightnessCap()); boolean changed = false; @@ -1770,10 +1775,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal maxBrightness); changed |= mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode, - mHbmController.getHighBrightnessMode()); + mBrightnessRangeController.getHighBrightnessMode()); changed |= mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint, - mHbmController.getTransitionPoint()); + mBrightnessRangeController.getTransitionPoint()); changed |= mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason, mBrightnessThrottler.getBrightnessMaxReason()); @@ -1783,10 +1788,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } void postBrightnessChangeRunnable() { - mHandler.post(mOnBrightnessChangeRunnable); + if (!mHandler.hasCallbacks(mOnBrightnessChangeRunnable)) { + mHandler.post(mOnBrightnessChangeRunnable); + } } - private HighBrightnessModeController createHbmControllerLocked() { + private HighBrightnessModeController createHbmControllerLocked( + HighBrightnessModeMetadata hbmMetadata, Runnable modeChangeCallback) { final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); final IBinder displayToken = @@ -1798,22 +1806,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData, - new HighBrightnessModeController.HdrBrightnessDeviceConfig() { - @Override - public float getHdrBrightnessFromSdr( - float sdrBrightness, float maxDesiredHdrSdrRatio) { - return mDisplayDeviceConfig.getHdrBrightnessFromSdr( - sdrBrightness, maxDesiredHdrSdrRatio); - } - }, - () -> { - sendUpdatePowerState(); - postBrightnessChangeRunnable(); - // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. - if (mAutomaticBrightnessController != null) { - mAutomaticBrightnessController.update(); - } - }, mHighBrightnessModeMetadata, mContext); + (sdrBrightness, maxDesiredHdrSdrRatio) -> + mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness, + maxDesiredHdrSdrRatio), modeChangeCallback, hbmMetadata, mContext); } private BrightnessThrottler createBrightnessThrottlerLocked() { @@ -1960,8 +1955,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal if (Float.isNaN(value)) { value = PowerManager.BRIGHTNESS_MIN; } - return MathUtils.constrain(value, - mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax()); + return MathUtils.constrain(value, mBrightnessRangeController.getCurrentBrightnessMin(), + mBrightnessRangeController.getCurrentBrightnessMax()); } private void animateScreenBrightness(float target, float sdrTarget, float rate) { @@ -2195,7 +2190,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal @Override public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) { - mHbmController.onAmbientLuxChange(ambientLux); + mBrightnessRangeController.onAmbientLuxChange(ambientLux); if (nits < 0) { mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness); } else { @@ -2374,8 +2369,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal dumpRbcEvents(pw); - if (mHbmController != null) { - mHbmController.dump(pw); + if (mBrightnessRangeController != null) { + mBrightnessRangeController.dump(pw); } if (mBrightnessThrottler != null) { @@ -2840,7 +2835,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal HysteresisLevels screenBrightnessThresholds, HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, - HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler, + BrightnessRangeController brightnessModeController, + BrightnessThrottler brightnessThrottler, BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux, float userBrightness) { return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor, @@ -2849,9 +2845,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal brighteningLightDebounceConfig, darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, - screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler, - idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong, - userLux, userBrightness); + screenBrightnessThresholdsIdle, context, brightnessModeController, + brightnessThrottler, idleModeBrightnessMapper, ambientLightHorizonShort, + ambientLightHorizonLong, userLux, userBrightness); } BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources, diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index c7c0fab6140d..7701bc6271ae 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 @@ final class LocalDisplayAdapter extends DisplayAdapter { 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/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java new file mode 100644 index 000000000000..dbabc2441224 --- /dev/null +++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java @@ -0,0 +1,94 @@ +/* + * 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.display; + +import android.annotation.NonNull; +import android.os.PowerManager; + +import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType; + +import java.util.HashMap; +import java.util.Map; + +/** + * Limits brightness for normal-brightness mode, based on ambient lux + **/ +class NormalBrightnessModeController { + @NonNull + private Map<BrightnessLimitMapType, Map<Float, Float>> mMaxBrightnessLimits = new HashMap<>(); + private float mAmbientLux = Float.MAX_VALUE; + private boolean mAutoBrightnessEnabled = false; + + // brightness limit in normal brightness mode, based on ambient lux. + private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX; + + boolean onAmbientLuxChange(float ambientLux) { + mAmbientLux = ambientLux; + return recalculateMaxBrightness(); + } + + boolean setAutoBrightnessState(int state) { + boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; + if (isEnabled != mAutoBrightnessEnabled) { + mAutoBrightnessEnabled = isEnabled; + return recalculateMaxBrightness(); + } + return false; + } + + float getCurrentBrightnessMax() { + return mMaxBrightness; + } + + boolean resetNbmData( + @NonNull Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessLimits) { + mMaxBrightnessLimits = maxBrightnessLimits; + return recalculateMaxBrightness(); + } + + private boolean recalculateMaxBrightness() { + float foundAmbientBoundary = Float.MAX_VALUE; + float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX; + + Map<Float, Float> maxBrightnessPoints = null; + + if (mAutoBrightnessEnabled) { + maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.ADAPTIVE); + } + + if (maxBrightnessPoints == null) { + maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.DEFAULT); + } + + if (maxBrightnessPoints != null) { + for (Map.Entry<Float, Float> brightnessPoint : maxBrightnessPoints.entrySet()) { + float ambientBoundary = brightnessPoint.getKey(); + // find ambient lux upper boundary closest to current ambient lux + if (ambientBoundary > mAmbientLux && ambientBoundary < foundAmbientBoundary) { + foundMaxBrightness = brightnessPoint.getValue(); + foundAmbientBoundary = ambientBoundary; + } + } + } + + if (mMaxBrightness != foundMaxBrightness) { + mMaxBrightness = foundMaxBrightness; + return true; + } + return false; + } +} 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 b4613a76c751..efb36227c7bd 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 @@ public class BatterySaverPolicy extends ContentObserver implements true, /* disableAod */ true, /* disableLaunchBoost */ true, /* disableOptionalSensors */ - true, /* disableVibration */ + false, /* disableVibration */ false, /* enableAdjustBrightness */ false, /* enableDataSaver */ true, /* enableFirewall */ diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index f96ca582c28f..7104a80c668d 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -46,6 +46,8 @@ <xs:annotation name="nonnull"/> <xs:annotation name="final"/> </xs:element> + <xs:element type="luxThrottling" name="luxThrottling" minOccurs="0" + maxOccurs="1"/> <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0" maxOccurs="1"/> <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1"/> @@ -137,6 +139,39 @@ </xs:sequence> </xs:complexType> + <xs:complexType name="luxThrottling"> + <xs:sequence> + <xs:element name="brightnessLimitMap" type="brightnessLimitMap" + maxOccurs="unbounded"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="brightnessLimitMap"> + <xs:sequence> + <xs:element name="type" type="PredefinedBrightnessLimitNames"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <!-- lux level from light sensor to screen brightness recommended max value map. + Screen brightness recommended max value is to highBrightnessMode.transitionPoint and must be below that --> + <xs:element name="map" type="nonNegativeFloatToFloatMap"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + </xs:sequence> + </xs:complexType> + + <!-- Predefined type names as defined by DisplayDeviceConfig.BrightnessLimitMapType --> + <xs:simpleType name="PredefinedBrightnessLimitNames"> + <xs:restriction base="xs:string"> + <xs:enumeration value="default"/> + <xs:enumeration value="adaptive"/> + </xs:restriction> + </xs:simpleType> + <xs:complexType name="highBrightnessMode"> <xs:all> <xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1" @@ -575,4 +610,27 @@ <xs:annotation name="final"/> </xs:element> </xs:complexType> + + <!-- generic types --> + <xs:complexType name="nonNegativeFloatToFloatPoint"> + <xs:sequence> + <xs:element name="first" type="nonNegativeDecimal"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <xs:element name="second" type="nonNegativeDecimal"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="nonNegativeFloatToFloatMap"> + <xs:sequence> + <xs:element name="point" type="nonNegativeFloatToFloatPoint" maxOccurs="unbounded"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + </xs:sequence> + </xs:complexType> </xs:schema> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index ad6434e0c545..507c9dccda59 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -26,6 +26,14 @@ package com.android.server.display.config { method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint(); } + public class BrightnessLimitMap { + ctor public BrightnessLimitMap(); + method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap(); + method @NonNull public final com.android.server.display.config.PredefinedBrightnessLimitNames getType(); + method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap); + method public final void setType(@NonNull com.android.server.display.config.PredefinedBrightnessLimitNames); + } + public class BrightnessThresholds { ctor public BrightnessThresholds(); method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints(); @@ -89,6 +97,7 @@ package com.android.server.display.config { method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle(); method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); method public final com.android.server.display.config.SensorDetails getLightSensor(); + method public com.android.server.display.config.LuxThrottling getLuxThrottling(); method @Nullable public final String getName(); method public final com.android.server.display.config.SensorDetails getProxSensor(); method public com.android.server.display.config.DisplayQuirks getQuirks(); @@ -115,6 +124,7 @@ package com.android.server.display.config { method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds); method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); method public final void setLightSensor(com.android.server.display.config.SensorDetails); + method public void setLuxThrottling(com.android.server.display.config.LuxThrottling); method public final void setName(@Nullable String); method public final void setProxSensor(com.android.server.display.config.SensorDetails); method public void setQuirks(com.android.server.display.config.DisplayQuirks); @@ -173,6 +183,11 @@ package com.android.server.display.config { method public java.util.List<java.math.BigInteger> getItem(); } + public class LuxThrottling { + ctor public LuxThrottling(); + method @NonNull public final java.util.List<com.android.server.display.config.BrightnessLimitMap> getBrightnessLimitMap(); + } + public class NitsMap { ctor public NitsMap(); method public String getInterpolation(); @@ -180,6 +195,19 @@ package com.android.server.display.config { method public void setInterpolation(String); } + public class NonNegativeFloatToFloatMap { + ctor public NonNegativeFloatToFloatMap(); + method @NonNull public final java.util.List<com.android.server.display.config.NonNegativeFloatToFloatPoint> getPoint(); + } + + public class NonNegativeFloatToFloatPoint { + ctor public NonNegativeFloatToFloatPoint(); + method @NonNull public final java.math.BigDecimal getFirst(); + method @NonNull public final java.math.BigDecimal getSecond(); + method public final void setFirst(@NonNull java.math.BigDecimal); + method public final void setSecond(@NonNull java.math.BigDecimal); + } + public class Point { ctor public Point(); method @NonNull public final java.math.BigDecimal getNits(); @@ -188,6 +216,12 @@ package com.android.server.display.config { method public final void setValue(@NonNull java.math.BigDecimal); } + public enum PredefinedBrightnessLimitNames { + method public String getRawName(); + enum_constant public static final com.android.server.display.config.PredefinedBrightnessLimitNames _default; + enum_constant public static final com.android.server.display.config.PredefinedBrightnessLimitNames adaptive; + } + public class RefreshRateConfigs { ctor public RefreshRateConfigs(); method public final java.math.BigInteger getDefaultPeakRefreshRate(); diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index 32243f04f6e8..212a243c6a9e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -2221,7 +2221,7 @@ public class GameManagerServiceTests { String[] packages = {mPackageName}; when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); } @@ -2238,12 +2238,12 @@ public class GameManagerServiceTests { doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1))) .when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean()); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); assertTrue(powerState.get(Mode.GAME)); gameManagerService.mUidObserver.onUidStateChanged( DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0); assertTrue(powerState.get(Mode.GAME)); gameManagerService.mUidObserver.onUidStateChanged( somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); @@ -2260,13 +2260,13 @@ public class GameManagerServiceTests { int somePackageId = DEFAULT_PACKAGE_UID + 1; when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false); } @@ -2277,9 +2277,9 @@ public class GameManagerServiceTests { String[] packages = {mPackageName}; when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false); } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java index 170076098b7d..4f98dca660df 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java @@ -815,7 +815,7 @@ public final class DisplayPowerController2Test { any(HysteresisLevels.class), any(HysteresisLevels.class), eq(mContext), - any(HighBrightnessModeController.class), + any(BrightnessRangeController.class), any(BrightnessThrottler.class), isNull(), anyInt(), @@ -1064,7 +1064,7 @@ public final class DisplayPowerController2Test { HysteresisLevels screenBrightnessThresholds, HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, - HighBrightnessModeController hbmController, + BrightnessRangeController brightnessRangeController, BrightnessThrottler brightnessThrottler, BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux, diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java index 5c0810fdca44..a93640b592cd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -819,7 +819,7 @@ public final class DisplayPowerControllerTest { any(HysteresisLevels.class), any(HysteresisLevels.class), eq(mContext), - any(HighBrightnessModeController.class), + any(BrightnessRangeController.class), any(BrightnessThrottler.class), isNull(), anyInt(), @@ -1038,7 +1038,7 @@ public final class DisplayPowerControllerTest { HysteresisLevels screenBrightnessThresholds, HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, - HighBrightnessModeController hbmController, + BrightnessRangeController brightnessRangeController, BrightnessThrottler brightnessThrottler, BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux, 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 b7dbaf93b9e2..f89f73c98cfd 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 static org.mockito.Mockito.when; 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 @@ public class LocalDisplayAdapterTest { 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 662477ddbbe9..8346050c3c89 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 android.hardware.biometrics.BiometricPrompt.DISMISSED_REASON_NEGAT 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.admin.DevicePolicyManager; 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 @@ public class AuthSessionTest { @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 @@ public class AuthSessionTest { } @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 @@ public class AuthSessionTest { 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 67be37616d5f..41f7dbcb0ff5 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 @@ package com.android.server.biometrics; 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 @@ public class BiometricServiceTest { .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 @@ public class BiometricServiceTest { } @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 000000000000..0c98c8d88d83 --- /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 e9d82696affc..89299002a227 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 @@ package com.android.server.biometrics.sensors; 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 @@ public class BiometricSchedulerTest { 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 d5d06d3b4eb8..046b01c831b5 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 @@ public class FaceAuthenticationClientTest { 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 f8f40fe44457..c383a96d5de3 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 @@ public class FingerprintAuthenticationClientTest { 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/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 962e86776ea2..a6acd60f3bd7 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -85,7 +85,7 @@ public class AutomaticBrightnessControllerTest { @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle; @Mock HysteresisLevels mScreenBrightnessThresholdsIdle; @Mock Handler mNoOpHandler; - @Mock HighBrightnessModeController mHbmController; + @Mock BrightnessRangeController mBrightnessRangeController; @Mock BrightnessThrottler mBrightnessThrottler; @Before @@ -134,12 +134,15 @@ public class AutomaticBrightnessControllerTest { DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle, - mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy, - AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness + mContext, mBrightnessRangeController, mBrightnessThrottler, + mIdleBrightnessMappingStrategy, AMBIENT_LIGHT_HORIZON_SHORT, + AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness ); - when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT); - when(mHbmController.getCurrentBrightnessMin()).thenReturn(BRIGHTNESS_MIN_FLOAT); + when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn( + BRIGHTNESS_MAX_FLOAT); + when(mBrightnessRangeController.getCurrentBrightnessMin()).thenReturn( + BRIGHTNESS_MIN_FLOAT); // Disable brightness throttling by default. Individual tests can enable it as needed. when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); when(mBrightnessThrottler.isThrottled()).thenReturn(false); diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java index 5837b21b89fd..708421d2a431 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -52,6 +52,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; @SmallTest @RunWith(AndroidJUnit4.class) @@ -376,6 +377,116 @@ public final class DisplayDeviceConfigTest { assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA); } + @Test + public void testValidLuxThrottling() throws Exception { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData = + mDisplayDeviceConfig.getLuxThrottlingData(); + assertEquals(2, luxThrottlingData.size()); + + Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get( + DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE); + assertEquals(2, adaptiveOnBrightnessPoints.size()); + assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA); + assertEquals(0.5f, adaptiveOnBrightnessPoints.get(5000f), SMALL_DELTA); + + Map<Float, Float> adaptiveOffBrightnessPoints = luxThrottlingData.get( + DisplayDeviceConfig.BrightnessLimitMapType.DEFAULT); + assertEquals(2, adaptiveOffBrightnessPoints.size()); + assertEquals(0.35f, adaptiveOffBrightnessPoints.get(1500f), SMALL_DELTA); + assertEquals(0.55f, adaptiveOffBrightnessPoints.get(5500f), SMALL_DELTA); + } + + @Test + public void testInvalidLuxThrottling() throws Exception { + setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getInvalidLuxThrottling())); + + Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData = + mDisplayDeviceConfig.getLuxThrottlingData(); + assertEquals(1, luxThrottlingData.size()); + + Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get( + DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE); + assertEquals(1, adaptiveOnBrightnessPoints.size()); + assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA); + } + + private String getValidLuxThrottling() { + return "<luxThrottling>\n" + + " <brightnessLimitMap>\n" + + " <type>adaptive</type>\n" + + " <map>\n" + + " <point>" + + " <first>1000</first>\n" + + " <second>0.3</second>\n" + + " </point>" + + " <point>" + + " <first>5000</first>\n" + + " <second>0.5</second>\n" + + " </point>" + + " </map>\n" + + " </brightnessLimitMap>\n" + + " <brightnessLimitMap>\n" + + " <type>default</type>\n" + + " <map>\n" + + " <point>" + + " <first>1500</first>\n" + + " <second>0.35</second>\n" + + " </point>" + + " <point>" + + " <first>5500</first>\n" + + " <second>0.55</second>\n" + + " </point>" + + " </map>\n" + + " </brightnessLimitMap>\n" + + "</luxThrottling>"; + } + + private String getInvalidLuxThrottling() { + return "<luxThrottling>\n" + + " <brightnessLimitMap>\n" + + " <type>adaptive</type>\n" + + " <map>\n" + + " <point>" + + " <first>1000</first>\n" + + " <second>0.3</second>\n" + + " </point>" + + " <point>" // second > hbm.transitionPoint, skipped + + " <first>1500</first>\n" + + " <second>0.9</second>\n" + + " </point>" + + " <point>" // same lux value, skipped + + " <first>1000</first>\n" + + " <second>0.5</second>\n" + + " </point>" + + " </map>\n" + + " </brightnessLimitMap>\n" + + " <brightnessLimitMap>\n" // Same type, skipped + + " <type>adaptive</type>\n" + + " <map>\n" + + " <point>" + + " <first>2000</first>\n" + + " <second>0.35</second>\n" + + " </point>" + + " <point>" + + " <first>6000</first>\n" + + " <second>0.55</second>\n" + + " </point>" + + " </map>\n" + + " </brightnessLimitMap>\n" + + " <brightnessLimitMap>\n" // Invalid points only, skipped + + " <type>default</type>\n" + + " <map>\n" + + " <point>" + + " <first>2500</first>\n" + + " <second>0.99</second>\n" + + " </point>" + + " </map>\n" + + " </brightnessLimitMap>\n" + + "</luxThrottling>"; + } + private String getRefreshThermalThrottlingMaps() { return "<refreshRateThrottlingMap>\n" + " <refreshRateThrottlingPoint>\n" @@ -405,6 +516,10 @@ public final class DisplayDeviceConfigTest { } private String getContent() { + return getContent(getValidLuxThrottling()); + } + + private String getContent(String brightnessCapConfig) { return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + "<displayConfiguration>\n" + "<name>Example Display</name>" @@ -462,6 +577,7 @@ public final class DisplayDeviceConfigTest { + "</point>\n" + "</sdrHdrRatioMap>\n" + "</highBrightnessMode>\n" + + brightnessCapConfig + "<screenOffBrightnessSensor>\n" + "<type>sensor_12345</type>\n" + "<name>Sensor 12345</name>\n" @@ -731,8 +847,12 @@ public final class DisplayDeviceConfigTest { } private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(getContent()); + } + + private void setupDisplayDeviceConfigFromDisplayConfigFile(String content) throws IOException { Path tempFile = Files.createTempFile("display_config", ".tmp"); - Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8)); + Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8)); mDisplayDeviceConfig = new DisplayDeviceConfig(mContext); mDisplayDeviceConfig.initFromFile(tempFile.toFile()); } diff --git a/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java new file mode 100644 index 000000000000..c379d6b79ee7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java @@ -0,0 +1,116 @@ +/* + * 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.display; + +import static org.junit.Assert.assertEquals; + +import android.os.PowerManager; + +import androidx.test.filters.SmallTest; + +import com.android.internal.annotations.Keep; +import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType; + +import com.google.common.collect.ImmutableMap; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashMap; +import java.util.Map; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; + +@SmallTest +@RunWith(JUnitParamsRunner.class) +public class NormalBrightnessModeControllerTest { + private static final float FLOAT_TOLERANCE = 0.001f; + + private final NormalBrightnessModeController mController = new NormalBrightnessModeController(); + + @Keep + private static Object[][] brightnessData() { + return new Object[][]{ + // no brightness config + {0, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, new HashMap<>(), + PowerManager.BRIGHTNESS_MAX}, + {0, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, new HashMap<>(), + PowerManager.BRIGHTNESS_MAX}, + {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, new HashMap<>(), + PowerManager.BRIGHTNESS_MAX}, + // Auto brightness - on, config only for default + {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of( + BrightnessLimitMapType.DEFAULT, + ImmutableMap.of(99f, 0.1f, 101f, 0.2f) + ), 0.2f}, + // Auto brightness - off, config only for default + {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of( + BrightnessLimitMapType.DEFAULT, + ImmutableMap.of(99f, 0.1f, 101f, 0.2f) + ), 0.2f}, + // Auto brightness - off, config only for adaptive + {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of( + BrightnessLimitMapType.ADAPTIVE, + ImmutableMap.of(99f, 0.1f, 101f, 0.2f) + ), PowerManager.BRIGHTNESS_MAX}, + // Auto brightness - on, config only for adaptive + {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of( + BrightnessLimitMapType.ADAPTIVE, + ImmutableMap.of(99f, 0.1f, 101f, 0.2f) + ), 0.2f}, + // Auto brightness - on, config for both + {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of( + BrightnessLimitMapType.DEFAULT, + ImmutableMap.of(99f, 0.1f, 101f, 0.2f), + BrightnessLimitMapType.ADAPTIVE, + ImmutableMap.of(99f, 0.3f, 101f, 0.4f) + ), 0.4f}, + // Auto brightness - off, config for both + {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of( + BrightnessLimitMapType.DEFAULT, + ImmutableMap.of(99f, 0.1f, 101f, 0.2f), + BrightnessLimitMapType.ADAPTIVE, + ImmutableMap.of(99f, 0.3f, 101f, 0.4f) + ), 0.2f}, + // Auto brightness - on, config for both, ambient high + {1000, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of( + BrightnessLimitMapType.DEFAULT, + ImmutableMap.of(1000f, 0.1f, 2000f, 0.2f), + BrightnessLimitMapType.ADAPTIVE, + ImmutableMap.of(99f, 0.3f, 101f, 0.4f) + ), PowerManager.BRIGHTNESS_MAX}, + }; + } + + @Test + @Parameters(method = "brightnessData") + public void testReturnsCorrectMaxBrightness(float ambientLux, int autoBrightnessState, + Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessConfig, + float expectedBrightness) { + setupController(ambientLux, autoBrightnessState, maxBrightnessConfig); + + assertEquals(expectedBrightness, mController.getCurrentBrightnessMax(), FLOAT_TOLERANCE); + } + + private void setupController(float ambientLux, int autoBrightnessState, + Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessConfig) { + mController.onAmbientLuxChange(ambientLux); + mController.setAutoBrightnessState(autoBrightnessState); + mController.resetNbmData(maxBrightnessConfig); + } +} 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 aa6ee09e0179..0b13f9a35c1f 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 @@ public class BatterySaverPolicyTest extends AndroidTestCase { 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 @@ public class BatterySaverPolicyTest extends AndroidTestCase { @SmallTest public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() { - testServiceDefaultValue_On(ServiceType.VIBRATION); + testServiceDefaultValue_Off(ServiceType.VIBRATION); } @SmallTest @@ -211,7 +211,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase { 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/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java index 6a1674b7df8e..63b8e1710b50 100644 --- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java +++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java @@ -38,13 +38,16 @@ import android.os.BatteryStatsInternal; import android.os.Process; import android.os.RemoteException; +import androidx.test.filters.FlakyTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.util.FakeLatencyTracker; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.Timeout; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.ArgumentCaptor; @@ -52,8 +55,12 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @RunWith(JUnit4.class) +@FlakyTest(bugId = 275746222) public class SoundTriggerMiddlewareLoggingLatencyTest { + @Rule + public Timeout mGlobalTimeout = Timeout.seconds(30); + private FakeLatencyTracker mLatencyTracker; @Mock private BatteryStatsInternal mBatteryStatsInternal; diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp index 90e61c52da68..0d2f2ef46cab 100644 --- a/tests/UiBench/Android.bp +++ b/tests/UiBench/Android.bp @@ -24,6 +24,5 @@ android_test { "androidx.recyclerview_recyclerview", "androidx.leanback_leanback", ], - certificate: "platform", test_suites: ["device-tests"], } diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml index 47211c5fbad1..4fc6ec71f29c 100644 --- a/tests/UiBench/AndroidManifest.xml +++ b/tests/UiBench/AndroidManifest.xml @@ -18,7 +18,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.android.test.uibench"> - <uses-permission android:name="android.permission.INJECT_EVENTS" /> <application android:allowBackup="false" android:theme="@style/Theme.AppCompat.Light.DarkActionBar" diff --git a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java index 1b2c3c60ffd4..06b65a7f9bbf 100644 --- a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java +++ b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java @@ -15,15 +15,11 @@ */ package com.android.test.uibench; -import android.app.Instrumentation; +import android.content.Intent; import android.os.Bundle; -import android.os.Looper; -import android.os.MessageQueue; -import androidx.appcompat.app.AppCompatActivity; -import android.view.KeyEvent; import android.widget.EditText; -import java.util.concurrent.Semaphore; +import androidx.appcompat.app.AppCompatActivity; /** * Note: currently incomplete, complexity of input continuously grows, instead of looping @@ -32,7 +28,13 @@ import java.util.concurrent.Semaphore; * Simulates typing continuously into an EditText. */ public class EditTextTypeActivity extends AppCompatActivity { - Thread mThread; + + /** + * Broadcast action: Used to notify UiBenchEditTextTypingMicrobenchmark test when the + * test activity was paused. + */ + private static final String ACTION_CANCEL_TYPING_CALLBACK = + "com.android.uibench.action.CANCEL_TYPING_CALLBACK"; private static String sSeedText = ""; static { @@ -46,9 +48,6 @@ public class EditTextTypeActivity extends AppCompatActivity { sSeedText = builder.toString(); } - final Object mLock = new Object(); - boolean mShouldStop = false; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -56,55 +55,13 @@ public class EditTextTypeActivity extends AppCompatActivity { EditText editText = new EditText(this); editText.setText(sSeedText); setContentView(editText); - - final Instrumentation instrumentation = new Instrumentation(); - final Semaphore sem = new Semaphore(0); - MessageQueue.IdleHandler handler = new MessageQueue.IdleHandler() { - @Override - public boolean queueIdle() { - // TODO: consider other signaling approaches - sem.release(); - return true; - } - }; - Looper.myQueue().addIdleHandler(handler); - synchronized (mLock) { - mShouldStop = false; - } - mThread = new Thread(new Runnable() { - int codes[] = { KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_L, - KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_SPACE }; - int i = 0; - @Override - public void run() { - while (true) { - try { - sem.acquire(); - } catch (InterruptedException e) { - // TODO, maybe - } - int code = codes[i % codes.length]; - if (i % 100 == 99) code = KeyEvent.KEYCODE_ENTER; - - synchronized (mLock) { - if (mShouldStop) break; - } - - // TODO: bit of a race here, since the event can arrive after pause/stop. - // (Can't synchronize on key send, since it's synchronous.) - instrumentation.sendKeyDownUpSync(code); - i++; - } - } - }); - mThread.start(); } @Override protected void onPause() { - synchronized (mLock) { - mShouldStop = true; - } + // Cancel the typing when the test activity was paused. + sendBroadcast(new Intent(ACTION_CANCEL_TYPING_CALLBACK).addFlags( + Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY)); super.onPause(); } } 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 000000000000..433e88c58851 --- /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 2a199d27a60e..ca12c4cb2020 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 @@ public class WifiNl80211Manager { // 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 @@ public class WifiNl80211Manager { /** @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 class WifiNl80211Manager { public WifiNl80211Manager(Context context) { mAlarmManager = context.getSystemService(AlarmManager.class); mEventHandler = new Handler(context.getMainLooper()); + mContext = context; } /** @@ -434,6 +443,7 @@ public class WifiNl80211Manager { if (mWificond == null) { Log.e(TAG, "Failed to get reference to wificond"); } + mContext = context; } /** @hide */ @@ -441,6 +451,7 @@ public class WifiNl80211Manager { public WifiNl80211Manager(Context context, IWificond wificond) { this(context); mWificond = wificond; + mContext = context; } /** @hide */ @@ -744,6 +755,9 @@ public class WifiNl80211Manager { 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 @@ public class WifiNl80211Manager { 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 @@ public class WifiNl80211Manager { 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 000000000000..ebff0e28082e --- /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 362eb1425a30..f12818fa64f9 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 @@ package android.net.wifi.nl80211; 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.ArgumentMatchers.eq; 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.verifyNoMoreInteractions; 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 @@ public class WifiNl80211ManagerTest { 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 @@ public class WifiNl80211ManagerTest { 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 @@ public class WifiNl80211ManagerTest { 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 @@ public class WifiNl80211ManagerTest { 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 @@ public class WifiNl80211ManagerTest { 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> { |