diff options
| author | 2018-09-21 04:34:59 +0000 | |
|---|---|---|
| committer | 2018-09-21 04:34:59 +0000 | |
| commit | c31c757852888d045a2d884e89fae4e069765db9 (patch) | |
| tree | 30be52e7e7ecafe6980175cfdc7cd68e11d1d39c | |
| parent | 6df15718f99401c31c11120ec14ca1a47236abf3 (diff) | |
| parent | 6cf54e8190c35d978970d7795f2783dc46c3571b (diff) | |
Merge changes from topic "face-dialog"
* changes:
3/n: Add FaceDialogView and "confirmation" plumbing
2/n: Refactor common code from FingerprintDialogView
1/n: Rename FingerprintDialogView to BiometricDialogView
30 files changed, 760 insertions, 470 deletions
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 20e0116a0473..66613ea50357 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -521,16 +521,16 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan */ public static String getErrorString(Context context, int errMsg, int vendorCode) { switch (errMsg) { - case FACE_ERROR_HW_UNAVAILABLE: - return context.getString( - com.android.internal.R.string.face_error_hw_not_available); case FACE_ERROR_UNABLE_TO_PROCESS: return context.getString( com.android.internal.R.string.face_error_unable_to_process); - case FACE_ERROR_TIMEOUT: - return context.getString(com.android.internal.R.string.face_error_timeout); + case FACE_ERROR_HW_UNAVAILABLE: + return context.getString( + com.android.internal.R.string.face_error_hw_not_available); case FACE_ERROR_NO_SPACE: return context.getString(com.android.internal.R.string.face_error_no_space); + case FACE_ERROR_TIMEOUT: + return context.getString(com.android.internal.R.string.face_error_timeout); case FACE_ERROR_CANCELED: return context.getString(com.android.internal.R.string.face_error_canceled); case FACE_ERROR_LOCKOUT: @@ -538,6 +538,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan case FACE_ERROR_LOCKOUT_PERMANENT: return context.getString( com.android.internal.R.string.face_error_lockout_permanent); + case FACE_ERROR_USER_CANCELED: + return context.getString(com.android.internal.R.string.face_error_user_canceled); case FACE_ERROR_NOT_ENROLLED: return context.getString(com.android.internal.R.string.face_error_not_enrolled); case FACE_ERROR_HW_NOT_PRESENT: diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index dd995c985286..a0122145c6e6 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -34,8 +34,8 @@ interface IFaceService { // This method invokes the BiometricDialog. The arguments are almost the same as above, // but should only be called from (BiometricPromptService). - void authenticateFromService(IBinder token, long sessionId, int userId, - IBiometricPromptServiceReceiver receiver, int flags, String opPackageName, + void authenticateFromService(boolean requireConfirmation, IBinder token, long sessionId, + int userId, IBiometricPromptServiceReceiver receiver, int flags, String opPackageName, in Bundle bundle, IBiometricPromptReceiver dialogReceiver, int callingUid, int callingPid, int callingUserId); diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index c4214cf19fa9..9b8f120ec08d 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -141,7 +141,8 @@ oneway interface IStatusBar void showShutdownUi(boolean isReboot, String reason); // Used to show the dialog when BiometricService starts authentication - void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type); + void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type, + boolean requireConfirmation); // Used to hide the dialog when a biometric is authenticated void onBiometricAuthenticated(); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index e48e73305191..90f2002d7363 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -91,7 +91,8 @@ interface IStatusBarService void showPinningEscapeToast(); // Used to show the dialog when BiometricService starts authentication - void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type); + void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type, + boolean requireConfirmation); // Used to hide the dialog when a biometric is authenticated void onBiometricAuthenticated(); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 64620f33b2df..6d0127a4bba1 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1439,6 +1439,10 @@ <string name="biometric_not_recognized">Not recognized</string> <!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] --> <string name="fingerprint_authenticated">Fingerprint authenticated</string> + <!-- Accessibility message announced when a face has been authenticated [CHAR LIMIT=NONE] --> + <string name="face_authenticated_no_confirmation_required">Face authenticated</string> + <!-- Accessibility message announced when a face has been authenticated, but requires the user to press the confirm button [CHAR LIMIT=NONE] --> + <string name="face_authenticated_confirmation_required">Face authenticated, please press confirm</string> <!-- Error message shown when the fingerprint hardware can't be accessed --> <string name="fingerprint_error_hw_not_available">Fingerprint hardware not available.</string> @@ -1516,6 +1520,8 @@ <string name="face_error_no_space">Face can\u2019t be stored.</string> <!-- Generic error message shown when the face operation (e.g. enrollment or authentication) is canceled. Generally not shown to the user. [CHAR LIMIT=50] --> <string name="face_error_canceled">Face operation canceled.</string> + <!-- Generic error message shown when the face authentication operation is canceled due to user input. Generally not shown to the user [CHAR LIMIT=50] --> + <string name="face_error_user_canceled">Face authentication canceled by user.</string> <!-- Generic error message shown when the face operation fails because too many attempts have been made. [CHAR LIMIT=50] --> <string name="face_error_lockout">Too many attempts. Try again later.</string> <!-- Generic error message shown when the face operation fails because strong authentication is required. [CHAR LIMIT=50] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7b8eceda9370..cf859867f21c 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2429,6 +2429,7 @@ <java-symbol type="string" name="face_error_timeout" /> <java-symbol type="array" name="face_error_vendor" /> <java-symbol type="string" name="face_error_canceled" /> + <java-symbol type="string" name="face_error_user_canceled" /> <java-symbol type="string" name="face_error_lockout" /> <java-symbol type="string" name="face_error_lockout_permanent" /> <java-symbol type="string" name="face_error_not_enrolled" /> @@ -2446,6 +2447,8 @@ <java-symbol type="string" name="face_acquired_not_detected" /> <java-symbol type="array" name="face_acquired_vendor" /> <java-symbol type="string" name="face_name_template" /> + <java-symbol type="string" name="face_authenticated_no_confirmation_required" /> + <java-symbol type="string" name="face_authenticated_confirmation_required" /> <!-- Face config --> <java-symbol type="integer" name="config_faceMaxTemplatesPerUser" /> diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_bg.xml b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml index 221f1701187d..335448d82373 100644 --- a/packages/SystemUI/res/drawable/fingerprint_dialog_bg.xml +++ b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml @@ -17,10 +17,10 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="@color/fingerprint_dialog_bg_color" /> + <solid android:color="@color/biometric_dialog_bg_color" /> <corners android:radius="1dp" - android:topLeftRadius="@dimen/fingerprint_dialog_corner_size" - android:topRightRadius="@dimen/fingerprint_dialog_corner_size" + android:topLeftRadius="@dimen/biometric_dialog_corner_size" + android:topRightRadius="@dimen/biometric_dialog_corner_size" android:bottomLeftRadius="0dp" android:bottomRightRadius="0dp"/> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/face_dialog_icon.xml b/packages/SystemUI/res/drawable/face_dialog_icon.xml new file mode 100644 index 000000000000..6d28b5a105f2 --- /dev/null +++ b/packages/SystemUI/res/drawable/face_dialog_icon.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" + android:width="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path android:fillColor="#000" android:pathData="M9,11.75A1.25,1.25 0 0,0 7.75,13A1.25,1.25 0 0,0 9,14.25A1.25,1.25 0 0,0 10.25,13A1.25,1.25 0 0,0 9,11.75M15,11.75A1.25,1.25 0 0,0 13.75,13A1.25,1.25 0 0,0 15,14.25A1.25,1.25 0 0,0 16.25,13A1.25,1.25 0 0,0 15,11.75M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,11.71 4,11.42 4.05,11.14C6.41,10.09 8.28,8.16 9.26,5.77C11.07,8.33 14.05,10 17.42,10C18.2,10 18.95,9.91 19.67,9.74C19.88,10.45 20,11.21 20,12C20,16.41 16.41,20 12,20Z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml index 83c1949b681c..05fd467b749e 100644 --- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml @@ -36,7 +36,7 @@ android:name="_R_G_L_2_G_D_0_P_0" android:pathData=" M-25.36 -24.41 C-25.93,-24.31 -26.49,-24.27 -26.81,-24.27 C-28.11,-24.27 -29.35,-24.62 -30.43,-25.4 C-32.11,-26.6 -33.2,-28.57 -33.2,-30.79 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_fingerprint_color" + android:strokeColor="@color/biometric_dialog_biometric_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -47,7 +47,7 @@ android:name="_R_G_L_2_G_D_1_P_0" android:pathData=" M-36.14 -21.78 C-37.15,-22.98 -37.72,-23.7 -38.51,-25.29 C-39.33,-26.94 -39.82,-28.78 -39.82,-30.77 C-39.82,-34.43 -36.85,-37.4 -33.19,-37.4 C-29.52,-37.4 -26.55,-34.43 -26.55,-30.77 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_fingerprint_color" + android:strokeColor="@color/biometric_dialog_biometric_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -58,7 +58,7 @@ android:name="_R_G_L_2_G_D_2_P_0" android:pathData=" M-42.19 -25.68 C-42.95,-27.82 -43.09,-29.54 -43.09,-30.8 C-43.09,-32.27 -42.84,-33.65 -42.27,-34.9 C-40.71,-38.35 -37.24,-40.75 -33.2,-40.75 C-27.71,-40.75 -23.26,-36.3 -23.26,-30.8 C-23.26,-28.97 -24.74,-27.49 -26.57,-27.49 C-28.4,-27.49 -29.89,-28.97 -29.89,-30.8 C-29.89,-32.64 -31.37,-34.12 -33.2,-34.12 C-35.04,-34.12 -36.52,-32.64 -36.52,-30.8 C-36.52,-28.23 -35.53,-25.92 -33.92,-24.22 C-32.69,-22.93 -31.48,-22.12 -29.44,-21.53 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_fingerprint_color" + android:strokeColor="@color/biometric_dialog_biometric_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -69,7 +69,7 @@ android:name="_R_G_L_2_G_D_3_P_0" android:pathData=" M-44.06 -38.17 C-42.87,-39.94 -41.39,-41.41 -39.51,-42.44 C-37.62,-43.47 -35.46,-44.05 -33.16,-44.05 C-30.88,-44.05 -28.72,-43.47 -26.85,-42.45 C-24.97,-41.43 -23.48,-39.97 -22.29,-38.21 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_fingerprint_color" + android:strokeColor="@color/biometric_dialog_biometric_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -80,7 +80,7 @@ android:name="_R_G_L_2_G_D_4_P_0" android:pathData=" M-25.72 -45.45 C-27.99,-46.76 -30.43,-47.52 -33.28,-47.52 C-36.13,-47.52 -38.51,-46.74 -40.62,-45.45 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_fingerprint_color" + android:strokeColor="@color/biometric_dialog_biometric_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -97,7 +97,7 @@ android:name="_R_G_L_1_G_D_0_P_0" android:pathData=" M0 -9 C4.97,-9 9,-4.97 9,0 C9,4.97 4.97,9 0,9 C-4.97,9 -9,4.97 -9,0 C-9,-4.97 -4.97,-9 0,-9c " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_error_color" + android:strokeColor="@color/biometric_dialog_error_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" @@ -118,7 +118,7 @@ <path android:name="_R_G_L_0_G_D_0_P_0" android:fillAlpha="1" - android:fillColor="@color/fingerprint_dialog_error_color" + android:fillColor="@color/biometric_dialog_error_color" android:fillType="nonZero" android:pathData=" M1.1 3.94 C1.1,4.55 0.61,5.04 0,5.04 C-0.61,5.04 -1.1,4.55 -1.1,3.94 C-1.1,3.33 -0.61,2.84 0,2.84 C0.61,2.84 1.1,3.33 1.1,3.94c " /> </group> @@ -131,7 +131,7 @@ <path android:name="_R_G_L_0_G_D_0_P_1" android:fillAlpha="1" - android:fillColor="@color/fingerprint_dialog_error_color" + android:fillColor="@color/biometric_dialog_error_color" android:fillType="nonZero" android:pathData=" M1 -4.06 C1,-4.06 1,-0.06 1,-0.06 C1,0.49 0.55,0.94 0,0.94 C-0.55,0.94 -1,0.49 -1,-0.06 C-1,-0.06 -1,-4.06 -1,-4.06 C-1,-4.61 -0.55,-5.06 0,-5.06 C0.55,-5.06 1,-4.61 1,-4.06c " /> </group> diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml index f682f87003dc..fd0ab22b2f98 100644 --- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml @@ -36,7 +36,7 @@ android:name="_R_G_L_3_G_D_0_P_0" android:pathData=" M-25.36 -24.41 C-25.93,-24.31 -26.49,-24.27 -26.81,-24.27 C-28.11,-24.27 -29.35,-24.62 -30.43,-25.4 C-32.11,-26.6 -33.2,-28.57 -33.2,-30.79 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_fingerprint_color" + android:strokeColor="@color/biometric_dialog_biometric_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -47,7 +47,7 @@ android:name="_R_G_L_3_G_D_1_P_0" android:pathData=" M-36.14 -21.78 C-37.15,-22.98 -37.72,-23.7 -38.51,-25.29 C-39.33,-26.94 -39.82,-28.78 -39.82,-30.77 C-39.82,-34.43 -36.85,-37.4 -33.19,-37.4 C-29.52,-37.4 -26.55,-34.43 -26.55,-30.77 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_fingerprint_color" + android:strokeColor="@color/biometric_dialog_biometric_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -58,7 +58,7 @@ android:name="_R_G_L_3_G_D_2_P_0" android:pathData=" M-42.19 -25.68 C-42.95,-27.82 -43.09,-29.54 -43.09,-30.8 C-43.09,-32.27 -42.84,-33.65 -42.27,-34.9 C-40.71,-38.35 -37.24,-40.75 -33.2,-40.75 C-27.71,-40.75 -23.26,-36.3 -23.26,-30.8 C-23.26,-28.97 -24.74,-27.49 -26.57,-27.49 C-28.4,-27.49 -29.89,-28.97 -29.89,-30.8 C-29.89,-32.64 -31.37,-34.12 -33.2,-34.12 C-35.04,-34.12 -36.52,-32.64 -36.52,-30.8 C-36.52,-28.23 -35.53,-25.92 -33.92,-24.22 C-32.69,-22.93 -31.48,-22.12 -29.44,-21.53 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_fingerprint_color" + android:strokeColor="@color/biometric_dialog_biometric_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -69,7 +69,7 @@ android:name="_R_G_L_3_G_D_3_P_0" android:pathData=" M-44.06 -38.17 C-42.87,-39.94 -41.39,-41.41 -39.51,-42.44 C-37.62,-43.47 -35.46,-44.05 -33.16,-44.05 C-30.88,-44.05 -28.72,-43.47 -26.85,-42.45 C-24.97,-41.43 -23.48,-39.97 -22.29,-38.21 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_fingerprint_color" + android:strokeColor="@color/biometric_dialog_biometric_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -80,7 +80,7 @@ android:name="_R_G_L_3_G_D_4_P_0" android:pathData=" M-25.72 -45.45 C-27.99,-46.76 -30.43,-47.52 -33.28,-47.52 C-36.13,-47.52 -38.51,-46.74 -40.62,-45.45 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_fingerprint_color" + android:strokeColor="@color/biometric_dialog_biometric_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -101,7 +101,7 @@ android:name="_R_G_L_2_G_D_0_P_0" android:pathData=" M-25.36 -24.41 C-25.93,-24.31 -26.49,-24.27 -26.81,-24.27 C-28.11,-24.27 -29.35,-24.62 -30.43,-25.4 C-32.11,-26.6 -33.2,-28.57 -33.2,-30.79 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_error_color" + android:strokeColor="@color/biometric_dialog_error_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -112,7 +112,7 @@ android:name="_R_G_L_2_G_D_1_P_0" android:pathData=" M-36.14 -21.78 C-37.15,-22.98 -37.72,-23.7 -38.51,-25.29 C-39.33,-26.94 -39.82,-28.78 -39.82,-30.77 C-39.82,-34.43 -36.85,-37.4 -33.19,-37.4 C-29.52,-37.4 -26.55,-34.43 -26.55,-30.77 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_error_color" + android:strokeColor="@color/biometric_dialog_error_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -123,7 +123,7 @@ android:name="_R_G_L_2_G_D_2_P_0" android:pathData=" M-42.19 -25.68 C-42.95,-27.82 -43.09,-29.54 -43.09,-30.8 C-43.09,-32.27 -42.84,-33.65 -42.27,-34.9 C-40.71,-38.35 -37.24,-40.75 -33.2,-40.75 C-27.71,-40.75 -23.26,-36.3 -23.26,-30.8 C-23.26,-28.97 -24.74,-27.49 -26.57,-27.49 C-28.4,-27.49 -29.89,-28.97 -29.89,-30.8 C-29.89,-32.64 -31.37,-34.12 -33.2,-34.12 C-35.04,-34.12 -36.52,-32.64 -36.52,-30.8 C-36.52,-28.23 -35.53,-25.92 -33.92,-24.22 C-32.69,-22.93 -31.48,-22.12 -29.44,-21.53 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_error_color" + android:strokeColor="@color/biometric_dialog_error_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -134,7 +134,7 @@ android:name="_R_G_L_2_G_D_3_P_0" android:pathData=" M-44.06 -38.17 C-42.87,-39.94 -41.39,-41.41 -39.51,-42.44 C-37.62,-43.47 -35.46,-44.05 -33.16,-44.05 C-30.88,-44.05 -28.72,-43.47 -26.85,-42.45 C-24.97,-41.43 -23.48,-39.97 -22.29,-38.21 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_error_color" + android:strokeColor="@color/biometric_dialog_error_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -145,7 +145,7 @@ android:name="_R_G_L_2_G_D_4_P_0" android:pathData=" M-25.72 -45.45 C-27.99,-46.76 -30.43,-47.52 -33.28,-47.52 C-36.13,-47.52 -38.51,-46.74 -40.62,-45.45 " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_error_color" + android:strokeColor="@color/biometric_dialog_error_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -162,7 +162,7 @@ android:name="_R_G_L_1_G_D_0_P_0" android:pathData=" M0 -9 C4.97,-9 9,-4.97 9,0 C9,4.97 4.97,9 0,9 C-4.97,9 -9,4.97 -9,0 C-9,-4.97 -4.97,-9 0,-9c " android:strokeAlpha="1" - android:strokeColor="@color/fingerprint_dialog_error_color" + android:strokeColor="@color/biometric_dialog_error_color" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" @@ -183,7 +183,7 @@ <path android:name="_R_G_L_0_G_D_0_P_0" android:fillAlpha="1" - android:fillColor="@color/fingerprint_dialog_error_color" + android:fillColor="@color/biometric_dialog_error_color" android:fillType="nonZero" android:pathData=" M1.1 3.94 C1.1,4.55 0.61,5.04 0,5.04 C-0.61,5.04 -1.1,4.55 -1.1,3.94 C-1.1,3.33 -0.61,2.84 0,2.84 C0.61,2.84 1.1,3.33 1.1,3.94c " /> </group> @@ -196,7 +196,7 @@ <path android:name="_R_G_L_0_G_D_0_P_1" android:fillAlpha="1" - android:fillColor="@color/fingerprint_dialog_error_color" + android:fillColor="@color/biometric_dialog_error_color" android:fillType="nonZero" android:pathData=" M1 -4.06 C1,-4.06 1,-0.06 1,-0.06 C1,0.49 0.55,0.94 0,0.94 C-0.55,0.94 -1,0.49 -1,-0.06 C-1,-0.06 -1,-4.06 -1,-4.06 C-1,-4.61 -0.55,-5.06 0,-5.06 C0.55,-5.06 1,-4.61 1,-4.06c " /> </group> diff --git a/packages/SystemUI/res/layout/fingerprint_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml index 1bdaf6e33544..0417e2ede2d3 100644 --- a/packages/SystemUI/res/layout/fingerprint_dialog.xml +++ b/packages/SystemUI/res/layout/biometric_dialog.xml @@ -19,7 +19,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="bottom" - android:background="@color/fingerprint_dialog_dim_color" + android:background="@color/biometric_dialog_dim_color" android:orientation="vertical"> <!-- This is not a Space since Spaces cannot be clicked --> @@ -47,7 +47,7 @@ android:layout_height="wrap_content" android:orientation="vertical" android:elevation="2dp" - android:background="@drawable/fingerprint_dialog_bg"> + android:background="@drawable/biometric_dialog_bg"> <TextView android:id="@+id/title" @@ -57,13 +57,13 @@ android:layout_marginEnd="24dp" android:layout_marginStart="24dp" android:layout_marginTop="24dp" - android:gravity="@integer/fingerprint_dialog_text_gravity" + android:gravity="@integer/biometric_dialog_text_gravity" android:textSize="20sp" android:maxLines="1" android:singleLine="true" android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" - android:textColor="@color/fingerprint_dialog_text_dark_color"/> + android:textColor="@color/biometric_dialog_text_dark_color"/> <TextView android:id="@+id/subtitle" @@ -72,13 +72,13 @@ android:layout_marginTop="8dp" android:layout_marginStart="24dp" android:layout_marginEnd="24dp" - android:gravity="@integer/fingerprint_dialog_text_gravity" + android:gravity="@integer/biometric_dialog_text_gravity" android:textSize="16sp" android:maxLines="1" android:singleLine="true" android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" - android:textColor="@color/fingerprint_dialog_text_dark_color"/> + android:textColor="@color/biometric_dialog_text_dark_color"/> <TextView android:id="@+id/description" @@ -86,20 +86,19 @@ android:layout_height="wrap_content" android:layout_marginEnd="24dp" android:layout_marginStart="24dp" - android:gravity="@integer/fingerprint_dialog_text_gravity" + android:gravity="@integer/biometric_dialog_text_gravity" android:paddingTop="8dp" android:textSize="16sp" android:maxLines="4" - android:textColor="@color/fingerprint_dialog_text_dark_color"/> + android:textColor="@color/biometric_dialog_text_dark_color"/> <ImageView - android:id="@+id/fingerprint_icon" - android:layout_width="@dimen/fingerprint_dialog_fp_icon_size" - android:layout_height="@dimen/fingerprint_dialog_fp_icon_size" + android:id="@+id/biometric_icon" + android:layout_width="@dimen/biometric_dialog_biometric_icon_size" + android:layout_height="@dimen/biometric_dialog_biometric_icon_size" android:layout_gravity="center_horizontal" android:layout_marginTop="48dp" - android:scaleType="fitXY" - android:contentDescription="@string/accessibility_fingerprint_dialog_fingerprint_icon" /> + android:scaleType="fitXY" /> <TextView android:id="@+id/error" @@ -112,9 +111,8 @@ android:textSize="12sp" android:gravity="center_horizontal" android:accessibilityLiveRegion="polite" - android:text="@string/fingerprint_dialog_touch_sensor" - android:contentDescription="@string/accessibility_fingerprint_dialog_help_area" - android:textColor="@color/fingerprint_dialog_text_light_color"/> + android:contentDescription="@string/accessibility_biometric_dialog_help_area" + android:textColor="@color/biometric_dialog_text_light_color"/> <LinearLayout android:layout_width="match_parent" @@ -125,7 +123,7 @@ android:orientation="horizontal" android:measureWithLargestChild="true"> <Space android:id="@+id/leftSpacer" - android:layout_width="24dp" + android:layout_width="12dp" android:layout_height="match_parent" android:visibility="visible" /> <!-- Negative Button --> @@ -133,20 +131,26 @@ android:layout_width="wrap_content" android:layout_height="match_parent" style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_marginStart="-12dp" - android:gravity="start|center_vertical" + android:gravity="center" android:maxLines="2" /> + <Space android:id="@+id/middleSpacer" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:visibility="visible" /> <!-- Positive Button --> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="match_parent" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_marginEnd="12dp" - android:maxLines="2" /> + style="@*android:style/Widget.DeviceDefault.Button.Colored" + android:gravity="center" + android:maxLines="2" + android:text="@string/biometric_dialog_confirm" + android:visibility="gone"/> <Space android:id="@+id/rightSpacer" - android:layout_width="24dip" + android:layout_width="12dip" android:layout_height="match_parent" - android:visibility="gone" /> + android:visibility="visible" /> </LinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 4920fb2ea407..d1320a34f365 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -160,13 +160,13 @@ <color name="smart_reply_button_background">#ffffffff</color> <color name="smart_reply_button_stroke">#ffdadce0</color> - <!-- Fingerprint dialog colors --> - <color name="fingerprint_dialog_bg_color">#ffffffff</color> <!-- 100% white --> - <color name="fingerprint_dialog_text_dark_color">#dd000000</color> <!-- 87% black --> - <color name="fingerprint_dialog_text_light_color">#89000000</color> <!-- 54% black --> - <color name="fingerprint_dialog_dim_color">#80000000</color> <!-- 50% black --> - <color name="fingerprint_dialog_error_color">#fff44336</color> <!-- red --> - <color name="fingerprint_dialog_fingerprint_color">#ff008577</color> <!-- teal --> + <!-- Biometric dialog colors --> + <color name="biometric_dialog_bg_color">#ffffffff</color> <!-- 100% white --> + <color name="biometric_dialog_text_dark_color">#dd000000</color> <!-- 87% black --> + <color name="biometric_dialog_text_light_color">#89000000</color> <!-- 54% black --> + <color name="biometric_dialog_dim_color">#80000000</color> <!-- 50% black --> + <color name="biometric_dialog_error_color">#fff44336</color> <!-- red --> + <color name="biometric_dialog_biometric_color">#ff008577</color> <!-- teal --> <!-- Logout button --> <color name="logout_button_bg_color">#ccffffff</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index c168d4e6d7b2..8ac3aa1748ce 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -982,10 +982,10 @@ the regular notification, when we have remote input history texts present. --> <dimen name="remote_input_history_extra_height">60dp</dimen> - <!-- Fingerprint Dialog values --> - <dimen name="fingerprint_dialog_fp_icon_size">64dp</dimen> - <dimen name="fingerprint_dialog_animation_translation_offset">350dp</dimen> - <dimen name="fingerprint_dialog_corner_size">4dp</dimen> + <!-- Biometric Dialog values --> + <dimen name="biometric_dialog_biometric_icon_size">64dp</dimen> + <dimen name="biometric_dialog_corner_size">4dp</dimen> + <dimen name="biometric_dialog_animation_translation_offset">350dp</dimen> <!-- Wireless Charging Animation values --> <dimen name="wireless_charging_dots_radius_start">0dp</dimen> diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml index 87c4bbba86ef..fd7a10500f36 100644 --- a/packages/SystemUI/res/values/integers.xml +++ b/packages/SystemUI/res/values/integers.xml @@ -15,7 +15,7 @@ ~ limitations under the License --> <resources> - <integer name="fingerprint_dialog_text_gravity">8388611</integer> <!-- gravity start --> + <integer name="biometric_dialog_text_gravity">8388611</integer> <!-- gravity start --> <!-- Action footer width used for layout_width to indicate WRAP_CONTENT (along with a weight of 0) as we can allow the carrier text to stretch as far as needed in the QS footer. --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 36f97cd64642..a3306d3dc4d5 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -257,14 +257,20 @@ <!-- Button name for "Cancel". [CHAR LIMIT=NONE] --> <string name="cancel">Cancel</string> + <!-- Content description for the error/help message are when the system-provided fingerprint dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_biometric_dialog_help_area">Help message area</string> + <!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] --> + <string name="biometric_dialog_confirm">Confirm</string> + <!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication --> <string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string> <!-- Content description of the fingerprint icon when the system-provided fingerprint dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_fingerprint_dialog_fingerprint_icon">Fingerprint icon</string> - <!-- Content description of the application icon when the system-provided fingerprint dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_fingerprint_dialog_app_icon">Application icon</string> - <!-- Content description for the error/help message are when the system-provided fingerprint dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_fingerprint_dialog_help_area">Help message area</string> + + <!-- Message shown when the system-provided face dialog is shown, asking for authentication [CHAR LIMIT=30] --> + <string name="face_dialog_looking_for_face">Looking for you\u2026</string> + <!-- Content description of the face icon when the system-provided face dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_face_dialog_face_icon">Face icon</string> <!-- Content description of the compatibility zoom button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_compatibility_zoom_button">Compatibility zoom button.</string> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java index 8fe577a4cec0..8fc46891c3a1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java @@ -18,6 +18,7 @@ package com.android.systemui.biometrics; import android.content.Context; import android.content.pm.PackageManager; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricPromptReceiver; import android.os.Bundle; @@ -31,9 +32,12 @@ import com.android.internal.os.SomeArgs; import com.android.systemui.SystemUI; import com.android.systemui.statusbar.CommandQueue; +import java.util.HashMap; +import java.util.Map; + /** * Receives messages sent from AuthenticationClient and shows the appropriate biometric UI (e.g. - * FingerprintDialogView). + * BiometricDialogView). */ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks { private static final String TAG = "BiometricDialogImpl"; @@ -48,7 +52,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private static final int MSG_USER_CANCELED = 7; private static final int MSG_BUTTON_POSITIVE = 8; - private FingerprintDialogView mDialogView; + private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view + private BiometricDialogView mCurrentDialog; private WindowManager mWindowManager; private IBiometricPromptReceiver mReceiver; private boolean mDialogShowing; @@ -111,16 +116,25 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba @Override public void start() { - if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { - return; + final PackageManager pm = mContext.getPackageManager(); + mDialogs = new HashMap<>(); + if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) { + mDialogs.put(BiometricAuthenticator.TYPE_FACE, new FaceDialogView(mContext, mCallback)); + } + if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { + mDialogs.put(BiometricAuthenticator.TYPE_FINGERPRINT, + new FingerprintDialogView(mContext, mCallback)); + } + + if (!mDialogs.isEmpty()) { + getComponent(CommandQueue.class).addCallbacks(this); + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } - getComponent(CommandQueue.class).addCallbacks(this); - mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - mDialogView = new FingerprintDialogView(mContext, mCallback); } @Override - public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type) { + public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type, + boolean requireConfirmation) { if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type); // Remove these messages as they are part of the previous client mHandler.removeMessages(MSG_BIOMETRIC_ERROR); @@ -129,6 +143,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; args.arg2 = receiver; + args.argi1 = type; + args.arg3 = requireConfirmation; mHandler.obtainMessage(MSG_SHOW_DIALOG, args).sendToTarget(); } @@ -157,33 +173,41 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } private void handleShowDialog(SomeArgs args) { + final int type = args.argi1; + mCurrentDialog = mDialogs.get(type); + if (DEBUG) Log.d(TAG, "handleShowDialog, isAnimatingAway: " - + mDialogView.isAnimatingAway()); - if (mDialogView.isAnimatingAway()) { - mDialogView.forceRemove(); + + mCurrentDialog.isAnimatingAway() + " type: " + type); + + if (mCurrentDialog.isAnimatingAway()) { + mCurrentDialog.forceRemove(); } else if (mDialogShowing) { Log.w(TAG, "Dialog already showing"); return; } mReceiver = (IBiometricPromptReceiver) args.arg2; - mDialogView.setBundle((Bundle)args.arg1); - mWindowManager.addView(mDialogView, mDialogView.getLayoutParams()); + mCurrentDialog.setBundle((Bundle)args.arg1); + mCurrentDialog.setRequireConfirmation((boolean)args.arg3); + mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams()); mDialogShowing = true; } private void handleBiometricAuthenticated() { if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated"); - // TODO: announce correct string depending on modality - mDialogView.announceForAccessibility( - mContext.getResources().getText( - com.android.internal.R.string.fingerprint_authenticated)); - handleHideDialog(false /* userCanceled */); + mCurrentDialog.announceForAccessibility( + mContext.getResources() + .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId())); + if (mCurrentDialog.requiresConfirmation()) { + mCurrentDialog.showConfirmationButton(); + } else { + handleHideDialog(false /* userCanceled */); + } } private void handleBiometricHelp(String message) { if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message); - mDialogView.showHelpMessage(message); + mCurrentDialog.showHelpMessage(message); } private void handleBiometricError(String error) { @@ -192,7 +216,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba if (DEBUG) Log.d(TAG, "Dialog already dismissed"); return; } - mDialogView.showErrorMessage(error); + mCurrentDialog.showErrorMessage(error); } private void handleHideDialog(boolean userCanceled) { @@ -212,7 +236,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } mReceiver = null; mDialogShowing = false; - mDialogView.startDismiss(); + mCurrentDialog.startDismiss(); } private void handleButtonNegative() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java new file mode 100644 index 000000000000..c90861e4af52 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.biometrics; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.hardware.biometrics.BiometricPrompt; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.Interpolator; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.systemui.Interpolators; +import com.android.systemui.R; + +/** + * Abstract base class. Shows a dialog for BiometricPrompt. + */ +public abstract class BiometricDialogView extends LinearLayout { + + private static final String TAG = "BiometricDialogView"; + + private static final int ANIMATION_DURATION_SHOW = 250; // ms + private static final int ANIMATION_DURATION_AWAY = 350; // ms + + private static final int MSG_CLEAR_MESSAGE = 1; + + protected static final int STATE_NONE = 0; + protected static final int STATE_AUTHENTICATING = 1; + protected static final int STATE_ERROR = 2; + protected static final int STATE_AUTHENTICATED = 3; + + private final IBinder mWindowToken = new Binder(); + private final Interpolator mLinearOutSlowIn; + private final WindowManager mWindowManager; + private final float mAnimationTranslationOffset; + private final int mErrorColor; + private final int mTextColor; + private final float mDisplayWidth; + private final DialogViewCallback mCallback; + + private ViewGroup mLayout; + private final TextView mErrorText; + private Bundle mBundle; + private final LinearLayout mDialog; + private int mLastState; + private boolean mAnimatingAway; + private boolean mWasForceRemoved; + protected boolean mRequireConfirmation; + + protected abstract void updateIcon(int lastState, int newState); + protected abstract int getHintStringResourceId(); + protected abstract int getAuthenticatedAccessibilityResourceId(); + protected abstract int getIconDescriptionResourceId(); + + private final Runnable mShowAnimationRunnable = new Runnable() { + @Override + public void run() { + mLayout.animate() + .alpha(1f) + .setDuration(ANIMATION_DURATION_SHOW) + .setInterpolator(mLinearOutSlowIn) + .withLayer() + .start(); + mDialog.animate() + .translationY(0) + .setDuration(ANIMATION_DURATION_SHOW) + .setInterpolator(mLinearOutSlowIn) + .withLayer() + .start(); + } + }; + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_CLEAR_MESSAGE: + handleClearMessage(); + break; + default: + Log.e(TAG, "Unhandled message: " + msg.what); + break; + } + } + }; + + public BiometricDialogView(Context context, DialogViewCallback callback) { + super(context); + mCallback = callback; + mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + mAnimationTranslationOffset = getResources() + .getDimension(R.dimen.biometric_dialog_animation_translation_offset); + mErrorColor = Color.parseColor( + getResources().getString(R.color.biometric_dialog_error_color)); + mTextColor = Color.parseColor( + getResources().getString(R.color.biometric_dialog_text_light_color)); + + DisplayMetrics metrics = new DisplayMetrics(); + mWindowManager.getDefaultDisplay().getMetrics(metrics); + mDisplayWidth = metrics.widthPixels; + + // Create the dialog + LayoutInflater factory = LayoutInflater.from(getContext()); + mLayout = (ViewGroup) factory.inflate(R.layout.biometric_dialog, this, false); + addView(mLayout); + + mDialog = mLayout.findViewById(R.id.dialog); + + mErrorText = mLayout.findViewById(R.id.error); + + mLayout.setOnKeyListener(new View.OnKeyListener() { + boolean downPressed = false; + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode != KeyEvent.KEYCODE_BACK) { + return false; + } + if (event.getAction() == KeyEvent.ACTION_DOWN && downPressed == false) { + downPressed = true; + } else if (event.getAction() == KeyEvent.ACTION_DOWN) { + downPressed = false; + } else if (event.getAction() == KeyEvent.ACTION_UP && downPressed == true) { + downPressed = false; + mCallback.onUserCanceled(); + } + return true; + } + }); + + final View space = mLayout.findViewById(R.id.space); + final View leftSpace = mLayout.findViewById(R.id.left_space); + final View rightSpace = mLayout.findViewById(R.id.right_space); + final Button negative = mLayout.findViewById(R.id.button2); + final Button positive = mLayout.findViewById(R.id.button1); + final ImageView icon = mLayout.findViewById(R.id.biometric_icon); + + icon.setContentDescription(getResources().getString(getIconDescriptionResourceId())); + mErrorText.setText(getResources().getString(getHintStringResourceId())); + + setDismissesDialog(space); + setDismissesDialog(leftSpace); + setDismissesDialog(rightSpace); + + negative.setOnClickListener((View v) -> { + mCallback.onNegativePressed(); + }); + + positive.setOnClickListener((View v) -> { + mCallback.onPositivePressed(); + }); + + mLayout.setFocusableInTouchMode(true); + mLayout.requestFocus(); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + final TextView title = mLayout.findViewById(R.id.title); + final TextView subtitle = mLayout.findViewById(R.id.subtitle); + final TextView description = mLayout.findViewById(R.id.description); + final Button negative = mLayout.findViewById(R.id.button2); + final Button positive = mLayout.findViewById(R.id.button1); + + mDialog.getLayoutParams().width = (int) mDisplayWidth; + + mLastState = STATE_NONE; + updateState(STATE_AUTHENTICATING); + + title.setText(mBundle.getCharSequence(BiometricPrompt.KEY_TITLE)); + title.setSelected(true); + + positive.setVisibility(View.INVISIBLE); + + final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE); + if (TextUtils.isEmpty(subtitleText)) { + subtitle.setVisibility(View.GONE); + } else { + subtitle.setVisibility(View.VISIBLE); + subtitle.setText(subtitleText); + } + + final CharSequence descriptionText = mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); + if (TextUtils.isEmpty(descriptionText)) { + description.setVisibility(View.GONE); + } else { + description.setVisibility(View.VISIBLE); + description.setText(descriptionText); + } + + negative.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT)); + + if (!mWasForceRemoved) { + // Dim the background and slide the dialog up + mDialog.setTranslationY(mAnimationTranslationOffset); + mLayout.setAlpha(0f); + postOnAnimation(mShowAnimationRunnable); + } else { + // Show the dialog immediately + mLayout.animate().cancel(); + mDialog.animate().cancel(); + mDialog.setAlpha(1.0f); + mDialog.setTranslationY(0); + mLayout.setAlpha(1.0f); + } + mWasForceRemoved = false; + } + + private void setDismissesDialog(View v) { + v.setClickable(true); + v.setOnTouchListener((View view, MotionEvent event) -> { + mCallback.onUserCanceled(); + return true; + }); + } + + public void startDismiss() { + mAnimatingAway = true; + + final Runnable endActionRunnable = new Runnable() { + @Override + public void run() { + mWindowManager.removeView(BiometricDialogView.this); + mAnimatingAway = false; + } + }; + + postOnAnimation(new Runnable() { + @Override + public void run() { + mLayout.animate() + .alpha(0f) + .setDuration(ANIMATION_DURATION_AWAY) + .setInterpolator(mLinearOutSlowIn) + .withLayer() + .start(); + mDialog.animate() + .translationY(mAnimationTranslationOffset) + .setDuration(ANIMATION_DURATION_AWAY) + .setInterpolator(mLinearOutSlowIn) + .withLayer() + .withEndAction(endActionRunnable) + .start(); + } + }); + } + + /** + * Force remove the window, cancelling any animation that's happening. This should only be + * called if we want to quickly show the dialog again (e.g. on rotation). Calling this method + * will cause the dialog to show without an animation the next time it's attached. + */ + public void forceRemove() { + mLayout.animate().cancel(); + mDialog.animate().cancel(); + mWindowManager.removeView(BiometricDialogView.this); + mAnimatingAway = false; + mWasForceRemoved = true; + } + + public boolean isAnimatingAway() { + return mAnimatingAway; + } + + public void setBundle(Bundle bundle) { + mBundle = bundle; + } + + public void setRequireConfirmation(boolean requireConfirmation) { + mRequireConfirmation = requireConfirmation; + } + + public boolean requiresConfirmation() { + return mRequireConfirmation; + } + + public void showConfirmationButton() { + final Button positive = mLayout.findViewById(R.id.button1); + positive.setVisibility(View.VISIBLE); + } + + public ViewGroup getLayout() { + return mLayout; + } + + // Clears the temporary message and shows the help message. + private void handleClearMessage() { + updateState(STATE_AUTHENTICATING); + mErrorText.setText(getHintStringResourceId()); + mErrorText.setTextColor(mTextColor); + } + + // Shows an error/help message + private void showTemporaryMessage(String message) { + mHandler.removeMessages(MSG_CLEAR_MESSAGE); + updateState(STATE_ERROR); + mErrorText.setText(message); + mErrorText.setTextColor(mErrorColor); + mErrorText.setContentDescription(message); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE), + BiometricPrompt.HIDE_DIALOG_DELAY); + } + + public void showHelpMessage(String message) { + showTemporaryMessage(message); + } + + public void showErrorMessage(String error) { + showTemporaryMessage(error); + mCallback.onErrorShown(); + } + + private void updateState(int newState) { + updateIcon(mLastState, newState); + mLastState = newState; + } + + public WindowManager.LayoutParams getLayoutParams() { + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + PixelFormat.TRANSLUCENT); + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; + lp.setTitle("BiometricDialogView"); + lp.token = mWindowToken; + return lp; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java new file mode 100644 index 000000000000..feef3a6dc133 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.biometrics; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.widget.ImageView; + +import com.android.systemui.R; + +/** + * This class loads the view for the system-provided dialog. The view consists of: + * Application Icon, Title, Subtitle, Description, Fingerprint Icon, Error/Help message area, + * and positive/negative buttons. + */ +public class FaceDialogView extends BiometricDialogView { + public FaceDialogView(Context context, + DialogViewCallback callback) { + super(context, callback); + } + + @Override + protected int getHintStringResourceId() { + return R.string.face_dialog_looking_for_face; + } + + @Override + protected int getAuthenticatedAccessibilityResourceId() { + if (mRequireConfirmation) { + return com.android.internal.R.string.face_authenticated_confirmation_required; + } else { + return com.android.internal.R.string.face_authenticated_no_confirmation_required; + } + } + + @Override + protected int getIconDescriptionResourceId() { + return R.string.accessibility_face_dialog_face_icon; + } + + @Override + protected void updateIcon(int lastState, int newState) { + Drawable icon = mContext.getDrawable(R.drawable.face_dialog_icon); + + final ImageView faceIcon = getLayout().findViewById(R.id.biometric_icon); + faceIcon.setImageDrawable(icon); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java index 68c2c42dd961..38a69a90f0bb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java @@ -17,32 +17,11 @@ package com.android.systemui.biometrics; import android.content.Context; -import android.graphics.Color; -import android.graphics.PixelFormat; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; -import android.hardware.biometrics.BiometricPrompt; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.text.TextUtils; -import android.util.DisplayMetrics; import android.util.Log; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.animation.Interpolator; -import android.widget.Button; import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import com.android.systemui.Interpolators; import com.android.systemui.R; /** @@ -50,288 +29,27 @@ import com.android.systemui.R; * Application Icon, Title, Subtitle, Description, Fingerprint Icon, Error/Help message area, * and positive/negative buttons. */ -public class FingerprintDialogView extends LinearLayout { - +public class FingerprintDialogView extends BiometricDialogView { private static final String TAG = "FingerprintDialogView"; - private static final int ANIMATION_DURATION_SHOW = 250; // ms - private static final int ANIMATION_DURATION_AWAY = 350; // ms - - private static final int MSG_CLEAR_MESSAGE = 1; - - private static final int STATE_NONE = 0; - private static final int STATE_FINGERPRINT = 1; - private static final int STATE_FINGERPRINT_ERROR = 2; - private static final int STATE_FINGERPRINT_AUTHENTICATED = 3; - - private final IBinder mWindowToken = new Binder(); - private final Interpolator mLinearOutSlowIn; - private final WindowManager mWindowManager; - private final float mAnimationTranslationOffset; - private final int mErrorColor; - private final int mTextColor; - private final int mFingerprintColor; - private final float mDisplayWidth; - private final DialogViewCallback mCallback; - - private ViewGroup mLayout; - private final TextView mErrorText; - private Bundle mBundle; - private final LinearLayout mDialog; - private int mLastState; - private boolean mAnimatingAway; - private boolean mWasForceRemoved; - - private final Runnable mShowAnimationRunnable = new Runnable() { - @Override - public void run() { - mLayout.animate() - .alpha(1f) - .setDuration(ANIMATION_DURATION_SHOW) - .setInterpolator(mLinearOutSlowIn) - .withLayer() - .start(); - mDialog.animate() - .translationY(0) - .setDuration(ANIMATION_DURATION_SHOW) - .setInterpolator(mLinearOutSlowIn) - .withLayer() - .start(); - } - }; - - private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch(msg.what) { - case MSG_CLEAR_MESSAGE: - handleClearMessage(); - break; - default: - Log.e(TAG, "Unhandled message: " + msg.what); - break; - } - } - }; - - public FingerprintDialogView(Context context, DialogViewCallback callback) { - super(context); - mCallback = callback; - mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; - mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - mAnimationTranslationOffset = getResources() - .getDimension(R.dimen.fingerprint_dialog_animation_translation_offset); - mErrorColor = Color.parseColor( - getResources().getString(R.color.fingerprint_dialog_error_color)); - mTextColor = Color.parseColor( - getResources().getString(R.color.fingerprint_dialog_text_light_color)); - mFingerprintColor = Color.parseColor( - getResources().getString(R.color.fingerprint_dialog_fingerprint_color)); - - DisplayMetrics metrics = new DisplayMetrics(); - mWindowManager.getDefaultDisplay().getMetrics(metrics); - mDisplayWidth = metrics.widthPixels; - - // Create the dialog - LayoutInflater factory = LayoutInflater.from(getContext()); - mLayout = (ViewGroup) factory.inflate(R.layout.fingerprint_dialog, this, false); - addView(mLayout); - - mDialog = mLayout.findViewById(R.id.dialog); - - mErrorText = mLayout.findViewById(R.id.error); - - mLayout.setOnKeyListener(new View.OnKeyListener() { - boolean downPressed = false; - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode != KeyEvent.KEYCODE_BACK) { - return false; - } - if (event.getAction() == KeyEvent.ACTION_DOWN && downPressed == false) { - downPressed = true; - } else if (event.getAction() == KeyEvent.ACTION_DOWN) { - downPressed = false; - } else if (event.getAction() == KeyEvent.ACTION_UP && downPressed == true) { - downPressed = false; - mCallback.onUserCanceled(); - } - return true; - } - }); - - final View space = mLayout.findViewById(R.id.space); - final View leftSpace = mLayout.findViewById(R.id.left_space); - final View rightSpace = mLayout.findViewById(R.id.right_space); - final Button negative = mLayout.findViewById(R.id.button2); - final Button positive = mLayout.findViewById(R.id.button1); - - setDismissesDialog(space); - setDismissesDialog(leftSpace); - setDismissesDialog(rightSpace); - - negative.setOnClickListener((View v) -> { - mCallback.onNegativePressed(); - }); - - positive.setOnClickListener((View v) -> { - mCallback.onPositivePressed(); - }); - - mLayout.setFocusableInTouchMode(true); - mLayout.requestFocus(); - } - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - - final TextView title = mLayout.findViewById(R.id.title); - final TextView subtitle = mLayout.findViewById(R.id.subtitle); - final TextView description = mLayout.findViewById(R.id.description); - final Button negative = mLayout.findViewById(R.id.button2); - final Button positive = mLayout.findViewById(R.id.button1); - - mDialog.getLayoutParams().width = (int) mDisplayWidth; - - mLastState = STATE_NONE; - updateFingerprintIcon(STATE_FINGERPRINT); - - title.setText(mBundle.getCharSequence(BiometricPrompt.KEY_TITLE)); - title.setSelected(true); - - final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE); - if (TextUtils.isEmpty(subtitleText)) { - subtitle.setVisibility(View.GONE); - } else { - subtitle.setVisibility(View.VISIBLE); - subtitle.setText(subtitleText); - } - - final CharSequence descriptionText = mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); - if (TextUtils.isEmpty(descriptionText)) { - description.setVisibility(View.GONE); - } else { - description.setVisibility(View.VISIBLE); - description.setText(descriptionText); - } - - negative.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT)); - - final CharSequence positiveText = - mBundle.getCharSequence(BiometricPrompt.KEY_POSITIVE_TEXT); - positive.setText(positiveText); // needs to be set for marquee to work - if (positiveText != null) { - positive.setVisibility(View.VISIBLE); - } else { - positive.setVisibility(View.GONE); - } - - if (!mWasForceRemoved) { - // Dim the background and slide the dialog up - mDialog.setTranslationY(mAnimationTranslationOffset); - mLayout.setAlpha(0f); - postOnAnimation(mShowAnimationRunnable); - } else { - // Show the dialog immediately - mLayout.animate().cancel(); - mDialog.animate().cancel(); - mDialog.setAlpha(1.0f); - mDialog.setTranslationY(0); - mLayout.setAlpha(1.0f); - } - mWasForceRemoved = false; - } - - private void setDismissesDialog(View v) { - v.setClickable(true); - v.setOnTouchListener((View view, MotionEvent event) -> { - mCallback.onUserCanceled(); - return true; - }); - } - - public void startDismiss() { - mAnimatingAway = true; - - final Runnable endActionRunnable = new Runnable() { - @Override - public void run() { - mWindowManager.removeView(FingerprintDialogView.this); - mAnimatingAway = false; - } - }; - - postOnAnimation(new Runnable() { - @Override - public void run() { - mLayout.animate() - .alpha(0f) - .setDuration(ANIMATION_DURATION_AWAY) - .setInterpolator(mLinearOutSlowIn) - .withLayer() - .start(); - mDialog.animate() - .translationY(mAnimationTranslationOffset) - .setDuration(ANIMATION_DURATION_AWAY) - .setInterpolator(mLinearOutSlowIn) - .withLayer() - .withEndAction(endActionRunnable) - .start(); - } - }); - } - - /** - * Force remove the window, cancelling any animation that's happening. This should only be - * called if we want to quickly show the dialog again (e.g. on rotation). Calling this method - * will cause the dialog to show without an animation the next time it's attached. - */ - public void forceRemove() { - mLayout.animate().cancel(); - mDialog.animate().cancel(); - mWindowManager.removeView(FingerprintDialogView.this); - mAnimatingAway = false; - mWasForceRemoved = true; + protected int getHintStringResourceId() { + return R.string.fingerprint_dialog_touch_sensor; } - public boolean isAnimatingAway() { - return mAnimatingAway; - } - - public void setBundle(Bundle bundle) { - mBundle = bundle; - } - - // Clears the temporary message and shows the help message. - private void handleClearMessage() { - updateFingerprintIcon(STATE_FINGERPRINT); - mErrorText.setText(R.string.fingerprint_dialog_touch_sensor); - mErrorText.setTextColor(mTextColor); - } - - // Shows an error/help message - private void showTemporaryMessage(String message) { - mHandler.removeMessages(MSG_CLEAR_MESSAGE); - updateFingerprintIcon(STATE_FINGERPRINT_ERROR); - mErrorText.setText(message); - mErrorText.setTextColor(mErrorColor); - mErrorText.setContentDescription(message); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE), - BiometricPrompt.HIDE_DIALOG_DELAY); - } - - public void showHelpMessage(String message) { - showTemporaryMessage(message); + @Override + protected int getAuthenticatedAccessibilityResourceId() { + return com.android.internal.R.string.fingerprint_authenticated; } - public void showErrorMessage(String error) { - showTemporaryMessage(error); - mCallback.onErrorShown(); + @Override + protected int getIconDescriptionResourceId() { + return R.string.accessibility_fingerprint_dialog_fingerprint_icon; } - private void updateFingerprintIcon(int newState) { - Drawable icon = getAnimationForTransition(mLastState, newState); + @Override + protected void updateIcon(int lastState, int newState) { + Drawable icon = getAnimationForTransition(lastState, newState); if (icon == null) { Log.e(TAG, "Animation not found"); @@ -342,25 +60,28 @@ public class FingerprintDialogView extends LinearLayout { ? (AnimatedVectorDrawable) icon : null; - final ImageView fingerprint_icon = mLayout.findViewById(R.id.fingerprint_icon); - fingerprint_icon.setImageDrawable(icon); + final ImageView fingerprintIcon = getLayout().findViewById(R.id.biometric_icon); + fingerprintIcon.setImageDrawable(icon); - if (animation != null && shouldAnimateForTransition(mLastState, newState)) { + if (animation != null && shouldAnimateForTransition(lastState, newState)) { animation.forceAnimationOnUI(); animation.start(); } + } - mLastState = newState; + public FingerprintDialogView(Context context, + DialogViewCallback callback) { + super(context, callback); } private boolean shouldAnimateForTransition(int oldState, int newState) { - if (oldState == STATE_NONE && newState == STATE_FINGERPRINT) { + if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { return false; - } else if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) { + } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { return true; - } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_FINGERPRINT) { + } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATING) { return true; - } else if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_AUTHENTICATED) { + } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) { // TODO(b/77328470): add animation when fingerprint is authenticated return false; } @@ -369,32 +90,18 @@ public class FingerprintDialogView extends LinearLayout { private Drawable getAnimationForTransition(int oldState, int newState) { int iconRes; - if (oldState == STATE_NONE && newState == STATE_FINGERPRINT) { + if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { iconRes = R.drawable.fingerprint_dialog_fp_to_error; - } else if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) { + } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { iconRes = R.drawable.fingerprint_dialog_fp_to_error; - } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_FINGERPRINT) { + } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATING) { iconRes = R.drawable.fingerprint_dialog_error_to_fp; - } else if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_AUTHENTICATED) { + } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) { // TODO(b/77328470): add animation when fingerprint is authenticated iconRes = R.drawable.fingerprint_dialog_error_to_fp; - } - else { + } else { return null; } return mContext.getDrawable(iconRes); } - - public WindowManager.LayoutParams getLayoutParams() { - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, - WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, - PixelFormat.TRANSLUCENT); - lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; - lp.setTitle("FingerprintDialogView"); - lp.token = mWindowToken; - return lp; - } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index e19c8441c44d..5c0b3289c61a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -161,7 +161,7 @@ public class CommandQueue extends IStatusBar.Stub { default void onRotationProposal(int rotation, boolean isValid) { } default void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, - int type) { } + int type, boolean requireConfirmation) { } default void onBiometricAuthenticated() { } default void onBiometricHelp(String message) { } default void onBiometricError(String error) { } @@ -514,12 +514,14 @@ public class CommandQueue extends IStatusBar.Stub { } @Override - public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type) { + public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type, + boolean requireConfirmation) { synchronized (mLock) { SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; args.arg2 = receiver; args.argi1 = type; + args.arg3 = requireConfirmation; mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args) .sendToTarget(); } @@ -763,7 +765,8 @@ public class CommandQueue extends IStatusBar.Stub { mCallbacks.get(i).showBiometricDialog( (Bundle) someArgs.arg1, (IBiometricPromptReceiver) someArgs.arg2, - someArgs.argi1); + someArgs.argi1, + (boolean) someArgs.arg3); } someArgs.recycle(); break; diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index 6b09f1bab364..65e55d66a4bb 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -26,10 +26,13 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; +import android.security.KeyStore; import android.util.Slog; import com.android.internal.statusbar.IStatusBarService; +import java.util.ArrayList; + /** * A class to keep track of the authentication state for a given client. */ @@ -50,28 +53,68 @@ public abstract class AuthenticationClient extends ClientMonitor { public static final int LOCKOUT_TIMED = 1; public static final int LOCKOUT_PERMANENT = 2; + private final boolean mRequireConfirmation; // Callback mechanism received from the client // (BiometricPrompt -> BiometricPromptService -> <Biometric>Service -> AuthenticationClient) private IBiometricPromptReceiver mDialogReceiverFromClient; private Bundle mBundle; private IStatusBarService mStatusBarService; private boolean mInLockout; + private TokenEscrow mEscrow; protected boolean mDialogDismissed; + /** + * Container that holds the identifier and authToken. For biometrics that require user + * confirmation, these should not be sent to their final destinations until the user confirms. + */ + class TokenEscrow { + final BiometricAuthenticator.Identifier mIdentifier; + final ArrayList<Byte> mToken; + + TokenEscrow(BiometricAuthenticator.Identifier identifier, ArrayList<Byte> token) { + mIdentifier = identifier; + mToken = token; + } + + BiometricAuthenticator.Identifier getIdentifier() { + return mIdentifier; + } + + ArrayList<Byte> getToken() { + return mToken; + } + } + // Receives events from SystemUI and handles them before forwarding them to BiometricDialog protected IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() { @Override // binder call public void onDialogDismissed(int reason) { if (mBundle != null && mDialogReceiverFromClient != null) { try { - mDialogReceiverFromClient.onDialogDismissed(reason); + if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) { + // Positive button is used by passive modalities as a "confirm" button, + // do not send to client + mDialogReceiverFromClient.onDialogDismissed(reason); + } if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) { onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED, 0 /* vendorCode */); + } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) { + // Have the service send the token to KeyStore, and send onAuthenticated + // to the application. + if (mEscrow != null) { + if (DEBUG) Slog.d(getLogTag(), "Confirmed"); + addTokenToKeyStore(mEscrow.getToken()); + notifyClientAuthenticationSucceeded(mEscrow.getIdentifier()); + mEscrow = null; + onAuthenticationConfirmed(); + } else { + Slog.e(getLogTag(), "Escrow is null!!!"); + } } mDialogDismissed = true; } catch (RemoteException e) { - Slog.e(getLogTag(), "Unable to notify dialog dismissed", e); + Slog.e(getLogTag(), "Remote exception", e); } stop(true /* initiatedByClient */); } @@ -89,11 +132,18 @@ public abstract class AuthenticationClient extends ClientMonitor { */ public abstract void onStop(); + /** + * This method is called when biometric authentication was confirmed by the user. The client + * should be removed. + */ + public abstract void onAuthenticationConfirmed(); + public AuthenticationClient(Context context, Metrics metrics, BiometricService.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricService.ServiceListener listener, int targetUserId, int groupId, long opId, boolean restricted, String owner, Bundle bundle, - IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService) { + IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService, + boolean requireConfirmation) { super(context, metrics, daemon, halDeviceId, token, listener, targetUserId, groupId, restricted, owner); mOpId = opId; @@ -101,6 +151,7 @@ public abstract class AuthenticationClient extends ClientMonitor { mDialogReceiverFromClient = dialogReceiver; mStatusBarService = statusBarService; mHandler = new Handler(Looper.getMainLooper()); + mRequireConfirmation = requireConfirmation; } @Override @@ -154,9 +205,41 @@ public abstract class AuthenticationClient extends ClientMonitor { return super.onError(deviceId, error, vendorCode); } + private void notifyClientAuthenticationSucceeded(BiometricAuthenticator.Identifier identifier) + throws RemoteException { + final BiometricService.ServiceListener listener = getListener(); + // Explicitly have if/else here to make it super obvious in case the code is + // touched in the future. + if (!getIsRestricted()) { + listener.onAuthenticationSucceeded( + getHalDeviceId(), identifier, getTargetUserId()); + } else { + listener.onAuthenticationSucceeded( + getHalDeviceId(), null, getTargetUserId()); + } + } + + private void addTokenToKeyStore(ArrayList<Byte> token) { + // Send the token to KeyStore + final byte[] byteToken = new byte[token.size()]; + for (int i = 0; i < token.size(); i++) { + byteToken[i] = token.get(i); + } + KeyStore.getInstance().addAuthToken(byteToken); + } + @Override public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier, - boolean authenticated) { + boolean authenticated, ArrayList<Byte> token) { + if (authenticated) { + if (mRequireConfirmation) { + // Store the token so it can be sent to keystore after the user presses confirm + mEscrow = new TokenEscrow(identifier, token); + } else { + addTokenToKeyStore(token); + } + } + boolean result = false; // If the biometric dialog is showing, notify authentication succeeded @@ -184,15 +267,8 @@ public abstract class AuthenticationClient extends ClientMonitor { Slog.v(getLogTag(), "onAuthenticated(owner=" + getOwnerString() + ", id=" + identifier.getBiometricId()); } - - // Explicitly have if/else here to make it super obvious in case the code is - // touched in the future. - if (!getIsRestricted()) { - listener.onAuthenticationSucceeded( - getHalDeviceId(), identifier, getTargetUserId()); - } else { - listener.onAuthenticationSucceeded( - getHalDeviceId(), null, getTargetUserId()); + if (!mRequireConfirmation) { + notifyClientAuthenticationSucceeded(identifier); } } } catch (RemoteException e) { @@ -241,7 +317,8 @@ public abstract class AuthenticationClient extends ClientMonitor { if (listener != null) { vibrateSuccess(); } - result |= true; // we have a valid biometric, done + // we have a valid biometric that doesn't require confirmation, done + result |= !mRequireConfirmation; resetFailedAttempts(); onStop(); } @@ -269,7 +346,7 @@ public abstract class AuthenticationClient extends ClientMonitor { if (mBundle != null) { try { mStatusBarService.showBiometricDialog(mBundle, mDialogReceiver, - getBiometricType()); + getBiometricType(), mRequireConfirmation); } catch (RemoteException e) { Slog.e(getLogTag(), "Unable to show biometric dialog", e); } diff --git a/services/core/java/com/android/server/biometrics/BiometricPromptService.java b/services/core/java/com/android/server/biometrics/BiometricPromptService.java index d1371d1e0bf5..35c9d3ed9a51 100644 --- a/services/core/java/com/android/server/biometrics/BiometricPromptService.java +++ b/services/core/java/com/android/server/biometrics/BiometricPromptService.java @@ -154,9 +154,9 @@ public class BiometricPromptService extends SystemService { } else if (mCurrentModality == BIOMETRIC_IRIS) { Slog.w(TAG, "Unsupported modality"); } else if (mCurrentModality == BIOMETRIC_FACE) { - mFaceService.authenticateFromService(token, sessionId, userId, - receiver, flags, opPackageName, bundle, dialogReceiver, - callingUid, callingPid, callingUserId); + mFaceService.authenticateFromService(true /* requireConfirmation */, token, + sessionId, userId, receiver, flags, opPackageName, bundle, + dialogReceiver, callingUid, callingPid, callingUserId); } else { Slog.w(TAG, "Unsupported modality"); } @@ -305,7 +305,9 @@ public class BiometricPromptService extends SystemService { try { receiver.onError(0 /* deviceId */, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS, - hardwareUnavailable); + FaceManager.getErrorString(getContext(), + BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS, + 0 /* vendorCode */)); } catch (RemoteException e) { Slog.e(TAG, "Unable to send error", e); } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 73c4223b48f8..c9b740d203cf 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -51,7 +51,6 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; -import android.security.KeyStore; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.SparseIntArray; @@ -225,10 +224,10 @@ public abstract class BiometricService extends SystemService implements IHwBinde IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, boolean restricted, String owner, Bundle bundle, IBiometricPromptReceiver dialogReceiver, - IStatusBarService statusBarService) { + IStatusBarService statusBarService, boolean requireConfirmation) { super(context, getMetrics(), daemon, halDeviceId, token, listener, targetUserId, groupId, opId, restricted, owner, bundle, dialogReceiver, - statusBarService); + statusBarService, requireConfirmation); } @Override @@ -279,6 +278,11 @@ public abstract class BiometricService extends SystemService implements IHwBinde } return AuthenticationClient.LOCKOUT_NONE; } + + @Override + public void onAuthenticationConfirmed() { + removeClient(mCurrentClient); + } } protected class EnrollClientImpl extends EnrollClient { @@ -587,14 +591,7 @@ public abstract class BiometricService extends SystemService implements IHwBinde ClientMonitor client = mCurrentClient; final boolean authenticated = identifier.getBiometricId() != 0; - if (authenticated) { - final byte[] byteToken = new byte[token.size()]; - for (int i = 0; i < token.size(); i++) { - byteToken[i] = token.get(i); - } - KeyStore.getInstance().addAuthToken(byteToken); - } - if (client != null && client.onAuthenticated(identifier, authenticated)) { + if (client != null && client.onAuthenticated(identifier, authenticated, token)) { removeClient(client); } if (authenticated) { diff --git a/services/core/java/com/android/server/biometrics/ClientMonitor.java b/services/core/java/com/android/server/biometrics/ClientMonitor.java index d30bed23ea97..408e26ab0efb 100644 --- a/services/core/java/com/android/server/biometrics/ClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/ClientMonitor.java @@ -28,6 +28,7 @@ import android.util.Slog; import com.android.internal.logging.MetricsLogger; +import java.util.ArrayList; import java.util.NoSuchElementException; /** @@ -128,7 +129,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { public abstract boolean onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining); public abstract boolean onAuthenticated(BiometricAuthenticator.Identifier identifier, - boolean authenticated); + boolean authenticated, ArrayList<Byte> token); public abstract boolean onRemoved(BiometricAuthenticator.Identifier identifier, int remaining); public abstract boolean onEnumerationResult( diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java index c305eca682be..7d186a925984 100644 --- a/services/core/java/com/android/server/biometrics/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/EnrollClient.java @@ -23,6 +23,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import java.util.ArrayList; import java.util.Arrays; /** @@ -126,7 +127,7 @@ public abstract class EnrollClient extends ClientMonitor { @Override public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier, - boolean authenticated) { + boolean authenticated, ArrayList<Byte> token) { if (DEBUG) Slog.w(getLogTag(), "onAuthenticated() called for enroll!"); return true; // Invalid for EnrollClient } diff --git a/services/core/java/com/android/server/biometrics/EnumerateClient.java b/services/core/java/com/android/server/biometrics/EnumerateClient.java index b76376962c0e..159d95edf869 100644 --- a/services/core/java/com/android/server/biometrics/EnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/EnumerateClient.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import java.util.ArrayList; + /** * A class to keep track of the enumeration state for a given client. */ @@ -94,7 +96,7 @@ public abstract class EnumerateClient extends ClientMonitor { @Override public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier, - boolean authenticated) { + boolean authenticated, ArrayList<Byte> token) { if (DEBUG) Slog.w(getLogTag(), "onAuthenticated() called for enumerate!"); return true; // Invalid for Enumerate. } diff --git a/services/core/java/com/android/server/biometrics/RemovalClient.java b/services/core/java/com/android/server/biometrics/RemovalClient.java index 3bf7f0404b1e..1995b9c313d4 100644 --- a/services/core/java/com/android/server/biometrics/RemovalClient.java +++ b/services/core/java/com/android/server/biometrics/RemovalClient.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import java.util.ArrayList; + /** * A class to keep track of the remove state for a given client. */ @@ -111,7 +113,7 @@ public abstract class RemovalClient extends ClientMonitor { @Override public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier, - boolean authenticated) { + boolean authenticated, ArrayList<Byte> token) { if (DEBUG) Slog.w(getLogTag(), "onAuthenticated() called for remove!"); return true; // Invalid for Remove. } diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 660710e34947..376eabc92875 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -88,10 +88,11 @@ public class FaceService extends BiometricService { DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, boolean restricted, String owner, Bundle bundle, - IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService) { + IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService, + boolean requireConfirmation) { super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId, - restricted, - owner, bundle, dialogReceiver, statusBarService); + restricted, owner, bundle, dialogReceiver, statusBarService, + requireConfirmation); } @Override @@ -159,15 +160,15 @@ public class FaceService extends BiometricService { final AuthenticationClientImpl client = new FaceAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, - null /* bundle */, null /* dialogReceiver */, mStatusBarService); - + null /* bundle */, null /* dialogReceiver */, mStatusBarService, + false /* requireConfirmation */); authenticateInternal(client, opId, opPackageName); } @Override // Binder call - public void authenticateFromService(IBinder token, long opId, int groupId, - IBiometricPromptServiceReceiver receiver, int flags, String opPackageName, - Bundle bundle, IBiometricPromptReceiver dialogReceiver, + public void authenticateFromService(boolean requireConfirmation, IBinder token, long opId, + int groupId, IBiometricPromptServiceReceiver receiver, int flags, + String opPackageName, Bundle bundle, IBiometricPromptReceiver dialogReceiver, int callingUid, int callingPid, int callingUserId) { checkPermission(USE_BIOMETRIC_INTERNAL); final boolean restricted = true; // BiometricPrompt is always restricted @@ -175,7 +176,7 @@ public class FaceService extends BiometricService { mDaemonWrapper, mHalDeviceId, token, new BiometricPromptServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, - bundle, dialogReceiver, mStatusBarService); + bundle, dialogReceiver, mStatusBarService, true /* requireConfirmation */); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index 9f4fff8157a2..1852aa84d817 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -110,10 +110,11 @@ public class FingerprintService extends BiometricService { DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, boolean restricted, String owner, Bundle bundle, - IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService) { + IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService, + boolean requireConfirmation) { super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId, - restricted, - owner, bundle, dialogReceiver, statusBarService); + restricted, owner, bundle, dialogReceiver, statusBarService, + requireConfirmation); } @Override @@ -182,8 +183,7 @@ public class FingerprintService extends BiometricService { final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId, opId, restricted, opPackageName, null /* bundle */, - null /* dialogReceiver */, mStatusBarService); - + null /* dialogReceiver */, mStatusBarService, false /* requireConfirmation */); authenticateInternal(client, opId, opPackageName); } @@ -198,7 +198,7 @@ public class FingerprintService extends BiometricService { mDaemonWrapper, mHalDeviceId, token, new BiometricPromptServiceListenerImpl(receiver), mCurrentUserId, groupId, opId, restricted, opPackageName, bundle, - dialogReceiver, mStatusBarService); + dialogReceiver, mStatusBarService, false /* requireConfirmation */); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index c2d8188649da..79eab6b12c10 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -565,11 +565,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override - public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type) { + public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type, + boolean requireConfirmation) { enforceBiometricDialog(); if (mBar != null) { try { - mBar.showBiometricDialog(bundle, receiver, type); + mBar.showBiometricDialog(bundle, receiver, type, requireConfirmation); } catch (RemoteException ex) { } } |