From 568d32935fbed7273cfc2e88baf213cce22e0175 Mon Sep 17 00:00:00 2001 From: joshmccloskey Date: Tue, 17 Sep 2019 14:02:23 -0700 Subject: [PATCH 1/9] Removing old confirm device credential logic Test: Verified that confirm device credential still works in the biometricpromptdemo app Bug: 140128468 Change-Id: I28fc3c0dc16677ad953284ffa9670d7abd34cd40 --- core/java/android/app/KeyguardManager.java | 6 - .../hardware/biometrics/BiometricManager.java | 50 ---- .../hardware/biometrics/BiometricPrompt.java | 33 +-- ...metricConfirmDeviceCredentialCallback.aidl | 26 -- .../biometrics/IBiometricService.aidl | 17 +- .../server/biometrics/BiometricService.java | 262 ++---------------- .../biometrics/BiometricServiceTest.java | 9 +- 7 files changed, 27 insertions(+), 376 deletions(-) delete mode 100644 core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 9b667a118ebc9..b1565ab8a501b 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -86,12 +86,6 @@ public class KeyguardManager { public static final String ACTION_CONFIRM_FRP_CREDENTIAL = "android.app.action.CONFIRM_FRP_CREDENTIAL"; - /** - * @hide - */ - public static final String EXTRA_BIOMETRIC_PROMPT_BUNDLE = - "android.app.extra.BIOMETRIC_PROMPT_BUNDLE"; - /** * A CharSequence dialog title to show to the user when used with a * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}. diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index d8110f33d7232..cbe8a052db2fc 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -201,55 +201,5 @@ public class BiometricManager { } } - /** - * TODO(b/123378871): Remove when moved. - * @hide - */ - @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public void onConfirmDeviceCredentialSuccess() { - if (mService != null) { - try { - mService.onConfirmDeviceCredentialSuccess(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } else { - Slog.w(TAG, "onConfirmDeviceCredentialSuccess(): Service not connected"); - } - } - - /** - * TODO(b/123378871): Remove when moved. - * @hide - */ - @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public void onConfirmDeviceCredentialError(int error, String message) { - if (mService != null) { - try { - mService.onConfirmDeviceCredentialError(error, message); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } else { - Slog.w(TAG, "onConfirmDeviceCredentialError(): Service not connected"); - } - } - - /** - * TODO(b/123378871): Remove when moved. - * @hide - */ - @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public void registerCancellationCallback(IBiometricConfirmDeviceCredentialCallback callback) { - if (mService != null) { - try { - mService.registerCancellationCallback(callback); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } else { - Slog.w(TAG, "registerCancellationCallback(): Service not connected"); - } - } } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index fb6b231632f19..301856d5676ae 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -82,11 +82,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @hide */ public static final String KEY_ALLOW_DEVICE_CREDENTIAL = "allow_device_credential"; - /** - * @hide - */ - public static final String KEY_FROM_CONFIRM_DEVICE_CREDENTIAL - = "from_confirm_device_credential"; /** * Error/help message will show for this amount of time. @@ -297,17 +292,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan return this; } - /** - * TODO(123378871): Remove when moved. - * @return - * @hide - */ - @RequiresPermission(USE_BIOMETRIC_INTERNAL) - @NonNull public Builder setFromConfirmDeviceCredential() { - mBundle.putBoolean(KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, true); - return this; - } - /** * Creates a {@link BiometricPrompt}. * @return a {@link BiometricPrompt} @@ -532,8 +516,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan public void authenticateUser(@NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, - int userId, - IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) { + int userId) { if (cancel == null) { throw new IllegalArgumentException("Must supply a cancellation signal"); } @@ -543,8 +526,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan if (callback == null) { throw new IllegalArgumentException("Must supply a callback"); } - authenticateInternal(null /* crypto */, cancel, executor, callback, userId, - confirmDeviceCredentialCallback); + authenticateInternal(null /* crypto */, cancel, executor, callback, userId); } /** @@ -595,8 +577,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)) { throw new IllegalArgumentException("Device credential not supported with crypto"); } - authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId(), - null /* confirmDeviceCredentialCallback */); + authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId()); } /** @@ -638,8 +619,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan if (callback == null) { throw new IllegalArgumentException("Must supply a callback"); } - authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId(), - null /* confirmDeviceCredentialCallback */); + authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId()); } private void cancelAuthentication() { @@ -656,8 +636,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, - int userId, - IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) { + int userId) { try { if (cancel.isCanceled()) { Log.w(TAG, "Authentication already canceled"); @@ -672,7 +651,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan final long sessionId = crypto != null ? crypto.getOpId() : 0; if (BiometricManager.hasBiometrics(mContext)) { mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver, - mContext.getOpPackageName(), mBundle, confirmDeviceCredentialCallback); + mContext.getOpPackageName(), mBundle); } else { mExecutor.execute(() -> { callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT, diff --git a/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl b/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl deleted file mode 100644 index 8b35852efd312..0000000000000 --- a/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2019 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.hardware.biometrics; - -/** - * Communication channel between ConfirmDeviceCredential / ConfirmLock* and BiometricService. - * @hide - */ -interface IBiometricConfirmDeviceCredentialCallback { - // Invoked when authentication should be canceled. - oneway void cancel(); -} \ No newline at end of file diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index f0a0b2f0235f2..6a3bf38a97e16 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -17,7 +17,6 @@ package android.hardware.biometrics; import android.os.Bundle; -import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -31,10 +30,8 @@ import android.hardware.biometrics.IBiometricServiceReceiver; interface IBiometricService { // Requests authentication. The service choose the appropriate biometric to use, and show // the corresponding BiometricDialog. - // TODO(b/123378871): Remove callback when moved. void authenticate(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle, - IBiometricConfirmDeviceCredentialCallback callback); + IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle); // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); @@ -57,16 +54,4 @@ interface IBiometricService { // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password) void resetLockout(in byte [] token); - - // TODO(b/123378871): Remove when moved. - // CDCA needs to send results to BiometricService if it was invoked using BiometricPrompt's - // setAllowDeviceCredential method, since there's no way for us to intercept onActivityResult. - // CDCA is launched from BiometricService (startActivityAsUser) instead of *ForResult. - void onConfirmDeviceCredentialSuccess(); - // TODO(b/123378871): Remove when moved. - void onConfirmDeviceCredentialError(int error, String message); - // TODO(b/123378871): Remove when moved. - // When ConfirmLock* is invoked from BiometricPrompt, it needs to register a callback so that - // it can receive the cancellation signal. - void registerCancellationCallback(IBiometricConfirmDeviceCredentialCallback callback); } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 3d341ef67d6fc..a3cdd22b8c50a 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -26,11 +26,9 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; import android.app.ActivityManager; import android.app.IActivityManager; -import android.app.KeyguardManager; import android.app.UserSwitchObserver; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.hardware.biometrics.BiometricAuthenticator; @@ -38,7 +36,6 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.BiometricsProtoEnums; -import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -95,11 +92,7 @@ public class BiometricService extends SystemService { private static final int MSG_ON_READY_FOR_AUTHENTICATION = 8; private static final int MSG_AUTHENTICATE = 9; private static final int MSG_CANCEL_AUTHENTICATION = 10; - private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS = 11; - private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR = 12; - private static final int MSG_REGISTER_CANCELLATION_CALLBACK = 13; - private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 14; - + private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11; private static final int[] FEATURE_ID = { TYPE_FINGERPRINT, TYPE_IRIS, @@ -131,20 +124,16 @@ public class BiometricService extends SystemService { * Authentication is successful, but we're waiting for the user to press "confirm" button. */ static final int STATE_AUTH_PENDING_CONFIRM = 5; - /** - * Biometric authentication was canceled, but the device is now showing ConfirmDeviceCredential - */ - static final int STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC = 6; /** * Biometric authenticated, waiting for SysUI to finish animation */ - static final int STATE_AUTHENTICATED_PENDING_SYSUI = 7; + static final int STATE_AUTHENTICATED_PENDING_SYSUI = 6; /** * Biometric error, waiting for SysUI to finish animation */ - static final int STATE_ERROR_PENDING_SYSUI = 8; + static final int STATE_ERROR_PENDING_SYSUI = 7; - final class AuthSession implements IBinder.DeathRecipient { + final class AuthSession { // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from // Services before we can start authenticating. Pairs that have been returned // are moved to mModalitiesMatched. @@ -184,14 +173,10 @@ public class BiometricService extends SystemService { // Timestamp when hardware authentication occurred private long mAuthenticatedTimeMs; - // TODO(b/123378871): Remove when moved. - private IBiometricConfirmDeviceCredentialCallback mConfirmDeviceCredentialCallback; - AuthSession(HashMap modalities, IBinder token, long sessionId, int userId, IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, int callingUid, int callingPid, int callingUserId, - int modality, boolean requireConfirmation, - IBiometricConfirmDeviceCredentialCallback callback) { + int modality, boolean requireConfirmation) { mModalitiesWaiting = modalities; mToken = token; mSessionId = sessionId; @@ -204,25 +189,12 @@ public class BiometricService extends SystemService { mCallingUserId = callingUserId; mModality = modality; mRequireConfirmation = requireConfirmation; - mConfirmDeviceCredentialCallback = callback; - - if (isFromConfirmDeviceCredential()) { - try { - token.linkToDeath(this, 0 /* flags */); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to link to death", e); - } - } } boolean isCrypto() { return mSessionId != 0; } - boolean isFromConfirmDeviceCredential() { - return mBundle.getBoolean(BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false); - } - boolean containsCookie(int cookie) { if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) { return true; @@ -233,24 +205,6 @@ public class BiometricService extends SystemService { return false; } - // TODO(b/123378871): Remove when moved. - @Override - public void binderDied() { - mHandler.post(() -> { - Slog.e(TAG, "Binder died, killing ConfirmDeviceCredential"); - if (mConfirmDeviceCredentialCallback == null) { - Slog.e(TAG, "Callback is null"); - return; - } - - try { - mConfirmDeviceCredentialCallback.cancel(); - mConfirmDeviceCredentialCallback = null; - } catch (RemoteException e) { - Slog.e(TAG, "Unable to send cancel", e); - } - }); - } } private final Injector mInjector; @@ -284,14 +238,6 @@ public class BiometricService extends SystemService { @VisibleForTesting AuthSession mPendingAuthSession; - // TODO(b/123378871): Remove when moved. - // When BiometricPrompt#setAllowDeviceCredentials is set to true, we need to store the - // client (app) receiver. BiometricService internally launches CDCA which invokes - // BiometricService to start authentication (normal path). When auth is success/rejected, - // CDCA will use an aidl method to poke BiometricService - the result will then be forwarded - // to this receiver. - private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver; - @VisibleForTesting final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override @@ -361,8 +307,7 @@ public class BiometricService extends SystemService { (Bundle) args.arg5 /* bundle */, args.argi2 /* callingUid */, args.argi3 /* callingPid */, - args.argi4 /* callingUserId */, - (IBiometricConfirmDeviceCredentialCallback) args.arg6 /* callback */); + args.argi4 /* callingUserId */); args.recycle(); break; } @@ -376,26 +321,6 @@ public class BiometricService extends SystemService { break; } - case MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS: { - handleOnConfirmDeviceCredentialSuccess(); - break; - } - - case MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR: { - SomeArgs args = (SomeArgs) msg.obj; - handleOnConfirmDeviceCredentialError( - args.argi1 /* error */, - (String) args.arg1 /* errorMsg */); - args.recycle(); - break; - } - - case MSG_REGISTER_CANCELLATION_CALLBACK: { - handleRegisterCancellationCallback( - (IBiometricConfirmDeviceCredentialCallback) msg.obj /* callback */); - break; - } - case MSG_ON_AUTHENTICATION_TIMED_OUT: { handleAuthenticationTimedOut((String) msg.obj /* errorMessage */); break; @@ -642,18 +567,12 @@ public class BiometricService extends SystemService { @Override // Binder call public void authenticate(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, - IBiometricConfirmDeviceCredentialCallback callback) + IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle) throws RemoteException { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); - // TODO(b/123378871): Remove when moved. - if (callback != null) { - checkInternalPermission(); - } - // In the BiometricServiceBase, check do the AppOps and foreground check. if (userId == callingUserId) { // Check the USE_BIOMETRIC permission here. @@ -670,12 +589,6 @@ public class BiometricService extends SystemService { return; } - final boolean isFromConfirmDeviceCredential = - bundle.getBoolean(BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false); - if (isFromConfirmDeviceCredential) { - checkInternalPermission(); - } - // Check the usage of this in system server. Need to remove this check if it becomes // a public API. final boolean useDefaultTitle = @@ -689,39 +602,6 @@ public class BiometricService extends SystemService { } } - // Launch CDC instead if necessary. CDC will return results through an AIDL call, since - // we can't get activity results. Store the receiver somewhere so we can forward the - // result back to the client. - // TODO(b/123378871): Remove when moved. - if (bundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL)) { - mHandler.post(() -> { - final KeyguardManager kgm = getContext().getSystemService( - KeyguardManager.class); - if (!kgm.isDeviceSecure()) { - try { - receiver.onError( - BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL, - getContext().getString( - R.string.biometric_error_device_not_secured)); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } - return; - } - mConfirmDeviceCredentialReceiver = receiver; - // Use this so we don't need to duplicate logic.. - final Intent intent = kgm.createConfirmDeviceCredentialIntent(null /* title */, - null /* description */, userId); - // Then give it the bundle to do magic behavior.. - intent.putExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE, bundle); - // Create a new task with this activity located at the root. - intent.setFlags( - Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); - getContext().startActivityAsUser(intent, UserHandle.CURRENT); - }); - return; - } - SomeArgs args = SomeArgs.obtain(); args.arg1 = token; args.arg2 = sessionId; @@ -732,40 +612,10 @@ public class BiometricService extends SystemService { args.argi2 = callingUid; args.argi3 = callingPid; args.argi4 = callingUserId; - args.arg6 = callback; mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget(); } - @Override // Binder call - public void onConfirmDeviceCredentialSuccess() { - checkInternalPermission(); - - mHandler.sendEmptyMessage(MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS); - } - - @Override // Binder call - public void onConfirmDeviceCredentialError(int error, String message) { - checkInternalPermission(); - - SomeArgs args = SomeArgs.obtain(); - args.argi1 = error; - args.arg1 = message; - mHandler.obtainMessage(MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR, args).sendToTarget(); - } - - @Override // Binder call - public void registerCancellationCallback( - IBiometricConfirmDeviceCredentialCallback callback) { - // TODO(b/123378871): Remove when moved. - // This callback replaces the one stored in the current session. If the session is null - // we can ignore this, since it means ConfirmDeviceCredential was launched by something - // else (not BiometricPrompt) - checkInternalPermission(); - - mHandler.obtainMessage(MSG_REGISTER_CANCELLATION_CALLBACK, callback).sendToTarget(); - } - @Override // Binder call public void cancelAuthentication(IBinder token, String opPackageName) throws RemoteException { @@ -1254,49 +1104,6 @@ public class BiometricService extends SystemService { } } - private void handleOnConfirmDeviceCredentialSuccess() { - if (mConfirmDeviceCredentialReceiver == null) { - Slog.w(TAG, "handleOnConfirmDeviceCredentialSuccess null!"); - return; - } - try { - mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded(); - if (mCurrentAuthSession != null) { - mCurrentAuthSession = null; - } - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException", e); - } - mConfirmDeviceCredentialReceiver = null; - } - - private void handleOnConfirmDeviceCredentialError(int error, String message) { - if (mConfirmDeviceCredentialReceiver == null) { - Slog.w(TAG, "handleOnConfirmDeviceCredentialError null! Error: " - + error + " " + message); - return; - } - try { - mConfirmDeviceCredentialReceiver.onError(error, message); - if (mCurrentAuthSession != null) { - mCurrentAuthSession = null; - } - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException", e); - } - mConfirmDeviceCredentialReceiver = null; - } - - private void handleRegisterCancellationCallback( - IBiometricConfirmDeviceCredentialCallback callback) { - if (mCurrentAuthSession == null) { - Slog.d(TAG, "Current auth session null"); - return; - } - Slog.d(TAG, "Updating cancel callback"); - mCurrentAuthSession.mConfirmDeviceCredentialCallback = callback; - } - private void handleOnError(int cookie, int error, String message) { Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie); // Errors can either be from the current auth session or the pending auth session. @@ -1311,18 +1118,9 @@ public class BiometricService extends SystemService { mCurrentAuthSession.mErrorEscrow = error; mCurrentAuthSession.mErrorStringEscrow = message; - if (mCurrentAuthSession.isFromConfirmDeviceCredential()) { - // If we were invoked by ConfirmDeviceCredential, do not delete the current - // auth session since we still need to respond to cancel signal while - if (DEBUG) Slog.d(TAG, "From CDC, transition to CANCELED_SHOWING_CDC state"); - - // Send the error to ConfirmDeviceCredential so that it goes to Pin/Pattern/Pass - // screen - mCurrentAuthSession.mClientReceiver.onError(error, message); - mCurrentAuthSession.mState = STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC; - mStatusBarService.hideBiometricDialog(); - } else if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { + if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI; + if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { mStatusBarService.hideBiometricDialog(); } else { @@ -1439,8 +1237,7 @@ public class BiometricService extends SystemService { mCurrentAuthSession.mCallingUid, mCurrentAuthSession.mCallingPid, mCurrentAuthSession.mCallingUserId, - mCurrentAuthSession.mModality, - mCurrentAuthSession.mConfirmDeviceCredentialCallback); + mCurrentAuthSession.mModality); } private void handleOnReadyForAuthentication(int cookie, boolean requireConfirmation, @@ -1498,8 +1295,7 @@ public class BiometricService extends SystemService { private void handleAuthenticate(IBinder token, long sessionId, int userId, IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, - int callingUid, int callingPid, int callingUserId, - IBiometricConfirmDeviceCredentialCallback callback) { + int callingUid, int callingPid, int callingUserId) { mHandler.post(() -> { final Pair result = checkAndGetBiometricModality(userId); @@ -1535,7 +1331,7 @@ public class BiometricService extends SystemService { // Start preparing for authentication. Authentication starts when // all modalities requested have invoked onReadyForAuthentication. authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, - callingUid, callingPid, callingUserId, modality, callback); + callingUid, callingPid, callingUserId, modality); }); } @@ -1550,8 +1346,7 @@ public class BiometricService extends SystemService { */ private void authenticateInternal(IBinder token, long sessionId, int userId, IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, - int callingUid, int callingPid, int callingUserId, int modality, - IBiometricConfirmDeviceCredentialCallback callback) { + int callingUid, int callingPid, int callingUserId, int modality) { try { boolean requireConfirmation = bundle.getBoolean( BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */); @@ -1571,7 +1366,7 @@ public class BiometricService extends SystemService { authenticators.put(modality, cookie); mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId, receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, - modality, requireConfirmation, callback); + modality, requireConfirmation); mPendingAuthSession.mState = STATE_AUTH_CALLED; // No polymorphism :( if ((modality & TYPE_FINGERPRINT) != 0) { @@ -1598,20 +1393,7 @@ public class BiometricService extends SystemService { return; } - if (mCurrentAuthSession != null - && mCurrentAuthSession.mState == STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC) { - if (DEBUG) Slog.d(TAG, "Cancel received while ConfirmDeviceCredential showing"); - try { - mCurrentAuthSession.mConfirmDeviceCredentialCallback.cancel(); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to cancel ConfirmDeviceCredential", e); - } - - // TODO(b/123378871): Remove when moved. Piggy back on this for now to clean up. - handleOnConfirmDeviceCredentialError(BiometricConstants.BIOMETRIC_ERROR_CANCELED, - getContext().getString(R.string.biometric_error_canceled)); - } else if (mCurrentAuthSession != null - && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { + if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { // We need to check the current authenticators state. If we're pending confirm // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client, // since we won't be getting an onError from the driver. @@ -1629,19 +1411,7 @@ public class BiometricService extends SystemService { Slog.e(TAG, "Remote exception", e); } } else { - boolean fromCDC = false; - if (mCurrentAuthSession != null) { - fromCDC = mCurrentAuthSession.mBundle.getBoolean( - BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false); - } - - if (fromCDC) { - if (DEBUG) Slog.d(TAG, "Cancelling from CDC"); - cancelInternal(token, opPackageName, false /* fromClient */); - } else { - cancelInternal(token, opPackageName, true /* fromClient */); - } - + cancelInternal(token, opPackageName, true /* fromClient */); } } 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 ccf3a908364a2..669780e46d120 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -55,6 +55,9 @@ import android.os.Handler; import android.os.IBinder; import android.security.KeyStore; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + import com.android.internal.R; import com.android.internal.statusbar.IStatusBarService; @@ -66,9 +69,6 @@ import org.mockito.MockitoAnnotations; import java.util.List; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; - @SmallTest public class BiometricServiceTest { @@ -727,8 +727,7 @@ public class BiometricServiceTest { 0 /* userId */, receiver, TEST_PACKAGE_NAME /* packageName */, - createTestBiometricPromptBundle(requireConfirmation), - null /* IBiometricConfirmDeviceCredentialCallback */); + createTestBiometricPromptBundle(requireConfirmation)); } private static Bundle createTestBiometricPromptBundle(boolean requireConfirmation) { From 396a84129fad2426bdbd104eff6fbf43cf3b5f69 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Mon, 16 Sep 2019 11:35:18 -0700 Subject: [PATCH 2/9] 11/n: Animate panel to full-screen when "Use Password" is pressed Bug: 140127687 Test: atest com.android.systemui.biometrics Test: BiometricPromptDemo, enable device credential, press password button Change-Id: I6a4c6ea7fb4a4f0c55faa049a8e7e71a1c5f19ff --- packages/SystemUI/res/values/strings.xml | 7 ++ .../biometrics/AuthBiometricView.java | 93 +++++++++++++++++-- .../biometrics/AuthContainerView.java | 10 ++ .../biometrics/AuthPanelController.java | 41 ++++++-- .../biometrics/AuthBiometricViewTest.java | 87 +++++++++++------ 5 files changed, 194 insertions(+), 44 deletions(-) diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c5547800360da..85d2e1ab9ef83 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -311,6 +311,13 @@ Authenticated + + Use PIN + + Use pattern + + Use password + Touch the fingerprint sensor diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index 73bbce9c5b356..4c4a745c0d58e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -16,8 +16,6 @@ package com.android.systemui.biometrics; -import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -25,6 +23,7 @@ import android.animation.ValueAnimator; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.hardware.biometrics.BiometricPrompt; import android.os.Bundle; @@ -34,7 +33,7 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.View; -import android.view.accessibility.AccessibilityEvent; +import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.widget.Button; import android.widget.ImageView; @@ -42,6 +41,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; import java.lang.annotation.Retention; @@ -97,6 +97,7 @@ public abstract class AuthBiometricView extends LinearLayout { int ACTION_BUTTON_NEGATIVE = 3; int ACTION_BUTTON_TRY_AGAIN = 4; int ACTION_ERROR = 5; + int ACTION_USE_DEVICE_CREDENTIAL = 6; /** * When an action has occurred. The caller will only invoke this when the callback should @@ -145,6 +146,10 @@ public abstract class AuthBiometricView extends LinearLayout { public int getDelayAfterError() { return BiometricPrompt.HIDE_DIALOG_DELAY; } + + public int getAnimationDuration() { + return AuthDialog.ANIMATE_DURATION_MS; + } } private final Injector mInjector; @@ -156,6 +161,7 @@ public abstract class AuthBiometricView extends LinearLayout { private AuthPanelController mPanelController; private Bundle mBundle; private boolean mRequireConfirmation; + private int mUserId; @AuthDialog.DialogSize int mSize = AuthDialog.SIZE_UNKNOWN; private TextView mTitleView; @@ -212,6 +218,9 @@ public abstract class AuthBiometricView extends LinearLayout { } else if (mSize == AuthDialog.SIZE_SMALL) { Log.w(TAG, "Ignoring background click during small dialog"); return; + } else if (mSize == AuthDialog.SIZE_LARGE) { + Log.w(TAG, "Ignoring background click during large dialog"); + return; } mCallback.onAction(Callback.ACTION_USER_CANCELED); }; @@ -267,6 +276,10 @@ public abstract class AuthBiometricView extends LinearLayout { backgroundView.setOnClickListener(mBackgroundClickListener); } + public void setUserId(int userId) { + mUserId = userId; + } + public void setRequireConfirmation(boolean requireConfirmation) { mRequireConfirmation = requireConfirmation; } @@ -305,10 +318,9 @@ public abstract class AuthBiometricView extends LinearLayout { // Animate the text final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1); - opacityAnimator.setDuration(AuthDialog.ANIMATE_DURATION_MS); + opacityAnimator.setDuration(mInjector.getAnimationDuration()); opacityAnimator.addUpdateListener((animation) -> { final float opacity = (float) animation.getAnimatedValue(); - mTitleView.setAlpha(opacity); mIndicatorView.setAlpha(opacity); mNegativeButton.setAlpha(opacity); @@ -324,7 +336,7 @@ public abstract class AuthBiometricView extends LinearLayout { // Choreograph together final AnimatorSet as = new AnimatorSet(); - as.setDuration(AuthDialog.ANIMATE_DURATION_MS); + as.setDuration(mInjector.getAnimationDuration()); as.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { @@ -360,6 +372,36 @@ public abstract class AuthBiometricView extends LinearLayout { mPanelController.updateForContentDimensions(mMediumWidth, mMediumHeight, false /* animate */); mSize = newSize; + } else if (newSize == AuthDialog.SIZE_LARGE) { + final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(1, 0); + opacityAnimator.setDuration(mInjector.getAnimationDuration()); + opacityAnimator.addUpdateListener((animation) -> { + final float opacity = (float) animation.getAnimatedValue(); + mTitleView.setAlpha(opacity); + mSubtitleView.setAlpha(opacity); + mDescriptionView.setAlpha(opacity); + mIconView.setAlpha(opacity); + mIndicatorView.setAlpha(opacity); + mNegativeButton.setAlpha(opacity); + }); + opacityAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + AuthBiometricView view = AuthBiometricView.this; + if (view.getParent() != null) { + ((ViewGroup) view.getParent()).removeView(view); + } + mSize = newSize; + } + }); + + mPanelController.setUseFullScreen(true); + mPanelController.updateForContentDimensions( + mPanelController.getContainerWidth(), + mPanelController.getContainerHeight(), + true /* animate */); + opacityAnimator.start(); } else { Log.e(TAG, "Unknown transition from: " + mSize + " to: " + newSize); } @@ -528,7 +570,12 @@ public abstract class AuthBiometricView extends LinearLayout { if (mState == STATE_PENDING_CONFIRMATION) { mCallback.onAction(Callback.ACTION_USER_CANCELED); } else { - mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE); + if (isDeviceCredentialAllowed()) { + updateSize(AuthDialog.SIZE_LARGE); + mCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL); + } else { + mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE); + } } }); @@ -557,7 +604,33 @@ public abstract class AuthBiometricView extends LinearLayout { @VisibleForTesting void onAttachedToWindowInternal() { setText(mTitleView, mBundle.getString(BiometricPrompt.KEY_TITLE)); - setText(mNegativeButton, mBundle.getString(BiometricPrompt.KEY_NEGATIVE_TEXT)); + + final String negativeText; + if (isDeviceCredentialAllowed()) { + final LockPatternUtils lpu = new LockPatternUtils(mContext); + switch (lpu.getKeyguardStoredPasswordQuality(mUserId)) { + case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: + negativeText = getResources().getString(R.string.biometric_dialog_use_pattern); + break; + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: + negativeText = getResources().getString(R.string.biometric_dialog_use_pin); + break; + case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: + case DevicePolicyManager.PASSWORD_QUALITY_MANAGED: + negativeText = getResources().getString(R.string.biometric_dialog_use_password); + break; + default: + negativeText = getResources().getString(R.string.biometric_dialog_use_password); + break; + } + + } else { + negativeText = mBundle.getString(BiometricPrompt.KEY_NEGATIVE_TEXT); + } + setText(mNegativeButton, negativeText); setTextOrHide(mSubtitleView, mBundle.getString(BiometricPrompt.KEY_SUBTITLE)); setTextOrHide(mDescriptionView, mBundle.getString(BiometricPrompt.KEY_DESCRIPTION)); @@ -655,4 +728,8 @@ public abstract class AuthBiometricView extends LinearLayout { } } } + + private boolean isDeviceCredentialAllowed() { + return mBundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 6555c75f677af..6781fd2c362c6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -25,6 +25,7 @@ import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricPrompt; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -169,6 +170,9 @@ public class AuthContainerView extends LinearLayout case AuthBiometricView.Callback.ACTION_ERROR: animateAway(AuthDialogCallback.DISMISSED_ERROR); break; + case AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL: + Log.v(TAG, "ACTION_USE_DEVICE_CREDENTIAL"); + break; default: Log.e(TAG, "Unhandled action: " + action); } @@ -228,6 +232,7 @@ public class AuthContainerView extends LinearLayout mBiometricView.setBiometricPromptBundle(config.mBiometricPromptBundle); mBiometricView.setCallback(mBiometricCallback); mBiometricView.setBackgroundView(mBackgroundView); + mBiometricView.setUserId(mConfig.mUserId); mScrollView = mContainerView.findViewById(R.id.scrollview); mScrollView.addView(mBiometricView); @@ -406,6 +411,11 @@ public class AuthContainerView extends LinearLayout }); } + private boolean isDeviceCredentialAllowed() { + return mConfig.mBiometricPromptBundle.getBoolean( + BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false); + } + private void sendPendingCallbackIfNotNull() { Log.d(TAG, "pendingCallback: " + mPendingCallbackReason); if (mPendingCallbackReason != null) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java index 55ba0491dc1ef..60c9ca49469b1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java @@ -16,6 +16,7 @@ package com.android.systemui.biometrics; +import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Outline; @@ -24,7 +25,6 @@ import android.view.View; import android.view.ViewOutlineProvider; import com.android.systemui.R; -import com.android.systemui.biometrics.AuthDialog; /** * Controls the back panel and its animations for the BiometricPrompt UI. @@ -32,13 +32,15 @@ import com.android.systemui.biometrics.AuthDialog; public class AuthPanelController extends ViewOutlineProvider { private static final String TAG = "BiometricPrompt/AuthPanelController"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; private final Context mContext; private final View mPanelView; private final float mCornerRadius; private final int mBiometricMargin; + private boolean mUseFullScreen; + private int mContainerWidth; private int mContainerHeight; @@ -49,11 +51,15 @@ public class AuthPanelController extends ViewOutlineProvider { public void getOutline(View view, Outline outline) { final int left = (mContainerWidth - mContentWidth) / 2; final int right = mContainerWidth - left; + + final int margin = mUseFullScreen ? 0 : mBiometricMargin; + final float cornerRadius = mUseFullScreen ? 0 : mCornerRadius; + final int top = mContentHeight < mContainerHeight - ? mContainerHeight - mContentHeight - mBiometricMargin - : mBiometricMargin; - final int bottom = mContainerHeight - mBiometricMargin; - outline.setRoundRect(left, top, right, bottom, mCornerRadius); + ? mContainerHeight - mContentHeight - margin + : margin; + final int bottom = mContainerHeight - margin; + outline.setRoundRect(left, top, right, bottom, cornerRadius); } public void setContainerDimensions(int containerWidth, int containerHeight) { @@ -64,6 +70,10 @@ public class AuthPanelController extends ViewOutlineProvider { mContainerHeight = containerHeight; } + public void setUseFullScreen(boolean fullScreen) { + mUseFullScreen = fullScreen; + } + public void updateForContentDimensions(int contentWidth, int contentHeight, boolean animate) { if (DEBUG) { Log.v(TAG, "Content Width: " + contentWidth @@ -78,12 +88,21 @@ public class AuthPanelController extends ViewOutlineProvider { if (animate) { ValueAnimator heightAnimator = ValueAnimator.ofInt(mContentHeight, contentHeight); - heightAnimator.setDuration(AuthDialog.ANIMATE_DURATION_MS); heightAnimator.addUpdateListener((animation) -> { mContentHeight = (int) animation.getAnimatedValue(); mPanelView.invalidateOutline(); }); heightAnimator.start(); + + ValueAnimator widthAnimator = ValueAnimator.ofInt(mContentWidth, contentWidth); + widthAnimator.addUpdateListener((animation) -> { + mContentWidth = (int) animation.getAnimatedValue(); + }); + + AnimatorSet as = new AnimatorSet(); + as.setDuration(AuthDialog.ANIMATE_DURATION_MS); + as.play(heightAnimator).with(widthAnimator); + as.start(); } else { mContentWidth = contentWidth; mContentHeight = contentHeight; @@ -91,6 +110,14 @@ public class AuthPanelController extends ViewOutlineProvider { } } + int getContainerWidth() { + return mContainerWidth; + } + + int getContainerHeight() { + return mContainerHeight; + } + AuthPanelController(Context context, View panelView) { mContext = context; mPanelView = panelView; diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java index 7a09137b1ff85..fa3006fbd0f2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java @@ -70,7 +70,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() { - initDialog(mContext, mCallback, new MockInjector()); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); // The onAuthenticated runnable is posted when authentication succeeds. mBiometricView.onAuthenticationSucceeded(); @@ -81,7 +81,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() { - initDialog(mContext, mCallback, new MockInjector()); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); mBiometricView.setRequireConfirmation(true); mBiometricView.onAuthenticationSucceeded(); @@ -97,7 +97,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testPositiveButton_sendsActionAuthenticated() { Button button = new Button(mContext); - initDialog(mContext, mCallback, new MockInjector() { + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { @Override public Button getPositiveButton() { return button; @@ -114,7 +114,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testNegativeButton_beforeAuthentication_sendsActionButtonNegative() { Button button = new Button(mContext); - initDialog(mContext, mCallback, new MockInjector() { + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { @Override public Button getNegativeButton() { return button; @@ -131,7 +131,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testNegativeButton_whenPendingConfirmation_sendsActionUserCanceled() { Button button = new Button(mContext); - initDialog(mContext, mCallback, new MockInjector() { + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { @Override public Button getNegativeButton() { return button; @@ -149,7 +149,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testTryAgainButton_sendsActionTryAgain() { Button button = new Button(mContext); - initDialog(mContext, mCallback, new MockInjector() { + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { @Override public Button getTryAgainButton() { return button; @@ -165,7 +165,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testError_sendsActionError() { - initDialog(mContext, mCallback, new MockInjector()); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); final String testError = "testError"; mBiometricView.onError(testError); waitForIdleSync(); @@ -176,7 +176,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testBackgroundClicked_sendsActionUserCanceled() { - initDialog(mContext, mCallback, new MockInjector()); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); View view = new View(mContext); mBiometricView.setBackgroundView(view); @@ -186,7 +186,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testBackgroundClicked_afterAuthenticated_neverSendsUserCanceled() { - initDialog(mContext, mCallback, new MockInjector()); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); View view = new View(mContext); mBiometricView.setBackgroundView(view); @@ -197,8 +197,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() { - initDialog(mContext, mCallback, new MockInjector()); - mBiometricView.setPanelController(mPanelController); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); mBiometricView.updateSize(AuthDialog.SIZE_SMALL); View view = new View(mContext); @@ -213,7 +212,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { Button tryAgainButton = new Button(mContext); TextView indicatorView = new TextView(mContext); - initDialog(mContext, mCallback, new MockInjector() { + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { @Override public Button getTryAgainButton() { return tryAgainButton; @@ -249,16 +248,18 @@ public class AuthBiometricViewTest extends SysuiTestCase { // Create new dialog and restore the previous state into it Button tryAgainButton2 = new Button(mContext); TextView indicatorView2 = new TextView(mContext); - initDialog(mContext, mCallback, state, new MockInjector() { - @Override - public Button getTryAgainButton() { - return tryAgainButton2; - } - @Override - public TextView getIndicatorView() { - return indicatorView2; - } - }); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, state, + new MockInjector() { + @Override + public Button getTryAgainButton() { + return tryAgainButton2; + } + + @Override + public TextView getIndicatorView() { + return indicatorView2; + } + }); mBiometricView.setRequireConfirmation(requireConfirmation); waitForIdleSync(); @@ -271,26 +272,49 @@ public class AuthBiometricViewTest extends SysuiTestCase { // dialog size is known. } - private Bundle buildBiometricPromptBundle() { + @Test + public void testNegativeButton_whenDeviceCredentialAllowed() throws InterruptedException { + Button negativeButton = new Button(mContext); + initDialog(mContext, true /* allowDeviceCredential */, mCallback, new MockInjector() { + @Override + public Button getNegativeButton() { + return negativeButton; + } + }); + + negativeButton.performClick(); + waitForIdleSync(); + + verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL); + } + + private Bundle buildBiometricPromptBundle(boolean allowDeviceCredential) { Bundle bundle = new Bundle(); bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title"); - bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative"); + if (allowDeviceCredential) { + bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true); + } else { + bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative"); + } return bundle; } - private void initDialog(Context context, AuthBiometricView.Callback callback, + private void initDialog(Context context, boolean allowDeviceCredential, + AuthBiometricView.Callback callback, Bundle savedState, MockInjector injector) { mBiometricView = new TestableBiometricView(context, null, injector); - mBiometricView.setBiometricPromptBundle(buildBiometricPromptBundle()); + mBiometricView.setBiometricPromptBundle(buildBiometricPromptBundle(allowDeviceCredential)); mBiometricView.setCallback(callback); mBiometricView.restoreState(savedState); mBiometricView.onFinishInflateInternal(); mBiometricView.onAttachedToWindowInternal(); + + mBiometricView.setPanelController(mPanelController); } - private void initDialog(Context context, AuthBiometricView.Callback callback, - MockInjector injector) { - initDialog(context, callback, null /* savedState */, injector); + private void initDialog(Context context, boolean allowDeviceCredential, + AuthBiometricView.Callback callback, MockInjector injector) { + initDialog(context, allowDeviceCredential, callback, null /* savedState */, injector); } private class MockInjector extends AuthBiometricView.Injector { @@ -338,6 +362,11 @@ public class AuthBiometricViewTest extends SysuiTestCase { public int getDelayAfterError() { return 0; // Keep this at 0 for tests to invoke callback immediately. } + + @Override + public int getAnimationDuration() { + return 0; + } } private class TestableBiometricView extends AuthBiometricView { From ff168dc49db48078f242d8c93210746b291cfbb9 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Mon, 16 Sep 2019 16:04:38 -0700 Subject: [PATCH 3/9] 12/n: Add LockPatternView for setDeviceCredentialAllowed(true) Includes lock icon, title, subtitle, description, lock pattern view. Corner radius and padding animates nicely from !=0 --> 0. Support for password/pin will come in a subsequent CL. Unit tests for AuthCredentialView will be added when password/pin are implemented. Support for persisting across configuration changes and landscape view will also be added in a subsequent change. Test: BiometricPromptDemo with the following: 1) Confirm pattern, callback received 2) Rejected, error string shown 3) Lockout (5 attempts), countdown string shown, pattern view disabled until countdown is over 4) Cancel pattern auth, callback received Test: atest BiometricServiceTest Test: atest com.android.systemui.biometrics Change-Id: Idc01e33be0074a6c8a43f60b172a4391bfbe5e8a --- .../hardware/biometrics/BiometricPrompt.java | 11 +- .../IBiometricServiceReceiverInternal.aidl | 2 + .../res/drawable/auth_dialog_lock.xml | 27 ++ .../res/layout/auth_container_view.xml | 2 +- .../res/layout/auth_credential_view.xml | 98 +++++++ packages/SystemUI/res/values/dimens.xml | 5 + packages/SystemUI/res/values/strings.xml | 8 + packages/SystemUI/res/values/styles.xml | 6 + .../biometrics/AuthBiometricView.java | 80 +++--- .../biometrics/AuthContainerView.java | 58 +++- .../systemui/biometrics/AuthController.java | 20 +- .../biometrics/AuthCredentialView.java | 261 ++++++++++++++++++ .../systemui/biometrics/AuthDialog.java | 25 +- .../biometrics/AuthDialogCallback.java | 14 +- .../biometrics/AuthPanelController.java | 55 ++-- .../biometrics/AuthBiometricViewTest.java | 7 +- .../biometrics/AuthContainerViewTest.java | 14 +- .../biometrics/AuthControllerTest.java | 17 +- .../server/biometrics/BiometricService.java | 43 ++- .../biometrics/BiometricServiceTest.java | 29 +- 20 files changed, 696 insertions(+), 86 deletions(-) create mode 100644 packages/SystemUI/res/drawable/auth_dialog_lock.xml create mode 100644 packages/SystemUI/res/layout/auth_credential_view.xml create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 301856d5676ae..342743da9c352 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -95,7 +95,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * @hide */ - public static final int DISMISSED_REASON_CONFIRMED = 1; + public static final int DISMISSED_REASON_BIOMETRIC_CONFIRMED = 1; /** * Dialog is done animating away after user clicked on the button set via @@ -114,7 +114,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * Authenticated, confirmation not required. Dialog animated away. * @hide */ - public static final int DISMISSED_REASON_CONFIRM_NOT_REQUIRED = 4; + public static final int DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED = 4; /** * Error message shown on SystemUI. When BiometricService receives this, the UI is already @@ -129,6 +129,11 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan */ public static final int DISMISSED_REASON_SERVER_REQUESTED = 6; + /** + * @hide + */ + public static final int DISMISSED_REASON_CREDENTIAL_CONFIRMED = 7; + private static class ButtonInfo { Executor executor; DialogInterface.OnClickListener listener; @@ -368,7 +373,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan @Override public void onDialogDismissed(int reason) throws RemoteException { // Check the reason and invoke OnClickListener(s) if necessary - if (reason == DISMISSED_REASON_CONFIRMED) { + if (reason == DISMISSED_REASON_BIOMETRIC_CONFIRMED) { mPositiveButtonInfo.executor.execute(() -> { mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); }); diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl index ca6114e4d8429..66b6e896fc136 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl @@ -38,4 +38,6 @@ oneway interface IBiometricServiceReceiverInternal { void onDialogDismissed(int reason); // Notifies that the user has pressed the "try again" button on SystemUI void onTryAgainPressed(); + // Notifies that the user has pressed the "use password" button on SystemUI + void onDeviceCredentialPressed(); } diff --git a/packages/SystemUI/res/drawable/auth_dialog_lock.xml b/packages/SystemUI/res/drawable/auth_dialog_lock.xml new file mode 100644 index 0000000000000..8146c16e4aaf4 --- /dev/null +++ b/packages/SystemUI/res/drawable/auth_dialog_lock.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/packages/SystemUI/res/layout/auth_container_view.xml b/packages/SystemUI/res/layout/auth_container_view.xml index 23199aacc0931..3db01a4e7f3aa 100644 --- a/packages/SystemUI/res/layout/auth_container_view.xml +++ b/packages/SystemUI/res/layout/auth_container_view.xml @@ -34,7 +34,7 @@ android:elevation="@dimen/biometric_dialog_elevation"/> + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 1079206c81ced..d722d618e416b 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1009,10 +1009,15 @@ 64dp 4dp + 350dp 4dp 1dp 16dp + + 100dp + + 60dp 0dp diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 85d2e1ab9ef83..8335c116c95ff 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -317,6 +317,14 @@ Use pattern Use password + + Wrong PIN + + Wrong pattern + + Wrong password + + Too many incorrect attempts.\nTry again in %d seconds. Touch the fingerprint sensor diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 6374191c4d7bb..96fbcbba6e8b4 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -314,6 +314,12 @@ ?android:attr/colorError + +