diff options
| -rw-r--r-- | services/core/java/com/android/server/biometrics/BiometricService.java | 1450 |
1 files changed, 820 insertions, 630 deletions
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index ddd416e14164..f313e1d48c53 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -57,6 +57,7 @@ import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -68,6 +69,7 @@ import android.util.Slog; import android.util.StatsLog; import com.android.internal.R; +import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.IStatusBarService; import com.android.server.SystemService; @@ -85,22 +87,137 @@ public class BiometricService extends SystemService { private static final String TAG = "BiometricService"; + private static final int MSG_ON_TASK_STACK_CHANGED = 1; + private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2; + private static final int MSG_ON_AUTHENTICATION_FAILED = 3; + private static final int MSG_ON_ERROR = 4; + private static final int MSG_ON_ACQUIRED = 5; + private static final int MSG_ON_DISMISSED = 6; + private static final int MSG_ON_TRY_AGAIN_PRESSED = 7; + 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[] FEATURE_ID = { TYPE_FINGERPRINT, TYPE_IRIS, TYPE_FACE }; + /** + * Authentication either just called and we have not transitioned to the CALLED state, or + * authentication terminated (success or error). + */ + private static final int STATE_AUTH_IDLE = 0; + /** + * Authentication was called and we are waiting for the <Biometric>Services to return their + * cookies before starting the hardware and showing the BiometricPrompt. + */ + private static final int STATE_AUTH_CALLED = 1; + /** + * Authentication started, BiometricPrompt is showing and the hardware is authenticating. + */ + private static final int STATE_AUTH_STARTED = 2; + /** + * Authentication is paused, waiting for the user to press "try again" button. Only + * passive modalities such as Face or Iris should have this state. Note that for passive + * modalities, the HAL enters the idle state after onAuthenticated(false) which differs from + * fingerprint. + */ + private static final int STATE_AUTH_PAUSED = 3; + /** + * Authentication is successful, but we're waiting for the user to press "confirm" button. + */ + private static final int STATE_AUTH_PENDING_CONFIRM = 5; + + private final class AuthSession { + // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from + // <Biometric>Services before we can start authenticating. Pairs that have been returned + // are moved to mModalitiesMatched. + final HashMap<Integer, Integer> mModalitiesWaiting; + // Pairs that have been matched. + final HashMap<Integer, Integer> mModalitiesMatched = new HashMap<>(); + + // The following variables are passed to authenticateInternal, which initiates the + // appropriate <Biometric>Services. + final IBinder mToken; + final long mSessionId; + final int mUserId; + // Original receiver from BiometricPrompt. + final IBiometricServiceReceiver mClientReceiver; + final String mOpPackageName; + // Info to be shown on BiometricDialog when all cookies are returned. + final Bundle mBundle; + final int mCallingUid; + final int mCallingPid; + final int mCallingUserId; + // Continue authentication with the same modality/modalities after "try again" is + // pressed + final int mModality; + final boolean mRequireConfirmation; + + // The current state, which can be either idle, called, or started + private int mState = STATE_AUTH_IDLE; + // For explicit confirmation, do not send to keystore until the user has confirmed + // the authentication. + byte[] mTokenEscrow; + + // Timestamp when hardware authentication occurred + private long mAuthenticatedTimeMs; + + AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId, + int userId, IBiometricServiceReceiver receiver, String opPackageName, + Bundle bundle, int callingUid, int callingPid, int callingUserId, + int modality, boolean requireConfirmation) { + mModalitiesWaiting = modalities; + mToken = token; + mSessionId = sessionId; + mUserId = userId; + mClientReceiver = receiver; + mOpPackageName = opPackageName; + mBundle = bundle; + mCallingUid = callingUid; + mCallingPid = callingPid; + mCallingUserId = callingUserId; + mModality = modality; + mRequireConfirmation = requireConfirmation; + } + + boolean isCrypto() { + return mSessionId != 0; + } + + boolean containsCookie(int cookie) { + if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) { + return true; + } + if (mModalitiesMatched != null && mModalitiesMatched.containsValue(cookie)) { + return true; + } + return false; + } + } + + private final class BiometricTaskStackListener extends TaskStackListener { + @Override + public void onTaskStackChanged() { + mHandler.sendEmptyMessage(MSG_ON_TASK_STACK_CHANGED); + } + } + private final AppOpsManager mAppOps; - private final Handler mHandler; private final boolean mHasFeatureFingerprint; private final boolean mHasFeatureIris; private final boolean mHasFeatureFace; private final SettingObserver mSettingObserver; private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks; + private final BiometricTaskStackListener mTaskStackListener = new BiometricTaskStackListener(); + private final Random mRandom = new Random(); private IFingerprintService mFingerprintService; private IFaceService mFaceService; + private IActivityTaskManager mActivityTaskManager; + private IStatusBarService mStatusBarService; // Get and cache the available authenticator (manager) classes. Used since aidl doesn't support // polymorphism :/ @@ -113,6 +230,108 @@ public class BiometricService extends SystemService { // should be safe. private int mCurrentModality; + // The current authentication session, null if idle/done. We need to track both the current + // and pending sessions since errors may be sent to either. + private AuthSession mCurrentAuthSession; + private AuthSession mPendingAuthSession; + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ON_TASK_STACK_CHANGED: { + handleTaskStackChanged(); + break; + } + + case MSG_ON_AUTHENTICATION_SUCCEEDED: { + SomeArgs args = (SomeArgs) msg.obj; + handleAuthenticationSucceeded( + (boolean) args.arg1 /* requireConfirmation */, + (byte[]) args.arg2 /* token */); + args.recycle(); + break; + } + + case MSG_ON_AUTHENTICATION_FAILED: { + SomeArgs args = (SomeArgs) msg.obj; + handleAuthenticationFailed( + args.argi1 /* cookie */, + (boolean) args.arg1 /* requireConfirmation */); + args.recycle(); + break; + } + + case MSG_ON_ERROR: { + SomeArgs args = (SomeArgs) msg.obj; + handleOnError( + args.argi1 /* cookie */, + args.argi2 /* error */, + (String) args.arg1 /* message */); + args.recycle(); + break; + } + + case MSG_ON_ACQUIRED: { + SomeArgs args = (SomeArgs) msg.obj; + handleOnAcquired( + args.argi1 /* acquiredInfo */, + (String) args.arg1 /* message */); + args.recycle(); + break; + } + + case MSG_ON_DISMISSED: { + handleOnDismissed(msg.arg1); + break; + } + + case MSG_ON_TRY_AGAIN_PRESSED: { + handleOnTryAgainPressed(); + break; + } + + case MSG_ON_READY_FOR_AUTHENTICATION: { + SomeArgs args = (SomeArgs) msg.obj; + handleOnReadyForAuthentication( + args.argi1 /* cookie */, + (boolean) args.arg1 /* requireConfirmation */, + args.argi2 /* userId */); + args.recycle(); + break; + } + + case MSG_AUTHENTICATE: { + SomeArgs args = (SomeArgs) msg.obj; + handleAuthenticate( + (IBinder) args.arg1 /* token */, + (long) args.arg2 /* sessionId */, + args.argi1 /* userid */, + (IBiometricServiceReceiver) args.arg3 /* receiver */, + (String) args.arg4 /* opPackageName */, + (Bundle) args.arg5 /* bundle */, + args.argi2 /* callingUid */, + args.argi3 /* callingPid */, + args.argi4 /* callingUserId */); + args.recycle(); + break; + } + + case MSG_CANCEL_AUTHENTICATION: { + SomeArgs args = (SomeArgs) msg.obj; + handleCancelAuthentication( + (IBinder) args.arg1 /* token */, + (String) args.arg2 /* opPackageName */); + args.recycle(); + break; + } + + default: + break; + } + } + }; + private final class Authenticator { int mType; BiometricAuthenticator mAuthenticator; @@ -251,142 +470,62 @@ public class BiometricService extends SystemService { } } - /** - * This is just a pass-through service that wraps Fingerprint, Iris, Face services. This service - * should not carry any state. The reality is we need to keep a tiny amount of state so that - * cancelAuthentication() can go to the right place. - */ - private final class BiometricServiceWrapper extends IBiometricService.Stub { + // Wrap the client's receiver so we can do things with the BiometricDialog first + private final IBiometricServiceReceiverInternal mInternalReceiver = + new IBiometricServiceReceiverInternal.Stub() { + @Override + public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token) + throws RemoteException { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = requireConfirmation; + args.arg2 = token; + mHandler.obtainMessage(MSG_ON_AUTHENTICATION_SUCCEEDED, args).sendToTarget(); + } - /** - * Authentication either just called and we have not transitioned to the CALLED state, or - * authentication terminated (success or error). - */ - private static final int STATE_AUTH_IDLE = 0; - /** - * Authentication was called and we are waiting for the <Biometric>Services to return their - * cookies before starting the hardware and showing the BiometricPrompt. - */ - private static final int STATE_AUTH_CALLED = 1; - /** - * Authentication started, BiometricPrompt is showing and the hardware is authenticating. - */ - private static final int STATE_AUTH_STARTED = 2; - /** - * Authentication is paused, waiting for the user to press "try again" button. Only - * passive modalities such as Face or Iris should have this state. Note that for passive - * modalities, the HAL enters the idle state after onAuthenticated(false) which differs from - * fingerprint. - */ - private static final int STATE_AUTH_PAUSED = 3; - /** - * Authentication is successful, but we're waiting for the user to press "confirm" button. - */ - private static final int STATE_AUTH_PENDING_CONFIRM = 5; - - final class AuthSession { - // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from - // <Biometric>Services before we can start authenticating. Pairs that have been returned - // are moved to mModalitiesMatched. - final HashMap<Integer, Integer> mModalitiesWaiting; - // Pairs that have been matched. - final HashMap<Integer, Integer> mModalitiesMatched = new HashMap<>(); - - // The following variables are passed to authenticateInternal, which initiates the - // appropriate <Biometric>Services. - final IBinder mToken; - final long mSessionId; - final int mUserId; - // Original receiver from BiometricPrompt. - final IBiometricServiceReceiver mClientReceiver; - final String mOpPackageName; - // Info to be shown on BiometricDialog when all cookies are returned. - final Bundle mBundle; - final int mCallingUid; - final int mCallingPid; - final int mCallingUserId; - // Continue authentication with the same modality/modalities after "try again" is - // pressed - final int mModality; - final boolean mRequireConfirmation; - - // The current state, which can be either idle, called, or started - private int mState = STATE_AUTH_IDLE; - // For explicit confirmation, do not send to keystore until the user has confirmed - // the authentication. - byte[] mTokenEscrow; - - // Timestamp when hardware authentication occurred - private long mAuthenticatedTimeMs; - - AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId, - int userId, IBiometricServiceReceiver receiver, String opPackageName, - Bundle bundle, int callingUid, int callingPid, int callingUserId, - int modality, boolean requireConfirmation) { - mModalitiesWaiting = modalities; - mToken = token; - mSessionId = sessionId; - mUserId = userId; - mClientReceiver = receiver; - mOpPackageName = opPackageName; - mBundle = bundle; - mCallingUid = callingUid; - mCallingPid = callingPid; - mCallingUserId = callingUserId; - mModality = modality; - mRequireConfirmation = requireConfirmation; - } + @Override + public void onAuthenticationFailed(int cookie, boolean requireConfirmation) + throws RemoteException { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = cookie; + args.arg1 = requireConfirmation; + mHandler.obtainMessage(MSG_ON_AUTHENTICATION_FAILED, args).sendToTarget(); + } - boolean isCrypto() { - return mSessionId != 0; - } + @Override + public void onError(int cookie, int error, String message) throws RemoteException { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = cookie; + args.argi2 = error; + args.arg1 = message; + mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget(); + } - boolean containsCookie(int cookie) { - if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) { - return true; - } - if (mModalitiesMatched != null && mModalitiesMatched.containsValue(cookie)) { - return true; - } - return false; - } + @Override + public void onAcquired(int acquiredInfo, String message) throws RemoteException { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = acquiredInfo; + args.arg1 = message; + mHandler.obtainMessage(MSG_ON_ACQUIRED, args).sendToTarget(); } - final class BiometricTaskStackListener extends TaskStackListener { - @Override - public void onTaskStackChanged() { - try { - final List<ActivityManager.RunningTaskInfo> runningTasks = - mActivityTaskManager.getTasks(1); - if (!runningTasks.isEmpty()) { - final String topPackage = runningTasks.get(0).topActivity.getPackageName(); - if (mCurrentAuthSession != null - && !topPackage.contentEquals(mCurrentAuthSession.mOpPackageName)) { - mStatusBarService.hideBiometricDialog(); - mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); - mCurrentAuthSession.mClientReceiver.onError( - BiometricConstants.BIOMETRIC_ERROR_CANCELED, - getContext().getString( - com.android.internal.R.string.biometric_error_canceled) - ); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; - } - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to get running tasks", e); - } - } + @Override + public void onDialogDismissed(int reason) throws RemoteException { + mHandler.obtainMessage(MSG_ON_DISMISSED, reason, 0 /* arg2 */).sendToTarget(); } - private final IActivityTaskManager mActivityTaskManager = getContext().getSystemService( - ActivityTaskManager.class).getService(); - private final IStatusBarService mStatusBarService = IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - private final BiometricTaskStackListener mTaskStackListener = - new BiometricTaskStackListener(); - private final Random mRandom = new Random(); + @Override + public void onTryAgainPressed() { + mHandler.sendEmptyMessage(MSG_ON_TRY_AGAIN_PRESSED); + } + }; + + /** + * This is just a pass-through service that wraps Fingerprint, Iris, Face services. This service + * should not carry any state. The reality is we need to keep a tiny amount of state so that + * cancelAuthentication() can go to the right place. + */ + private final class BiometricServiceWrapper extends IBiometricService.Stub { // 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 @@ -395,331 +534,15 @@ public class BiometricService extends SystemService { // to this receiver. private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver; - // The current authentication session, null if idle/done. We need to track both the current - // and pending sessions since errors may be sent to either. - private AuthSession mCurrentAuthSession; - private AuthSession mPendingAuthSession; - - // Wrap the client's receiver so we can do things with the BiometricDialog first - private final IBiometricServiceReceiverInternal mInternalReceiver = - new IBiometricServiceReceiverInternal.Stub() { - @Override - public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token) - throws RemoteException { - try { - // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded - // after user dismissed/canceled dialog). - if (mCurrentAuthSession == null) { - Slog.e(TAG, "onAuthenticationSucceeded(): Auth session is null"); - return; - } - - if (!requireConfirmation) { - mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); - KeyStore.getInstance().addAuthToken(token); - mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; - } else { - mCurrentAuthSession.mAuthenticatedTimeMs = System.currentTimeMillis(); - // Store the auth token and submit it to keystore after the confirmation - // button has been pressed. - mCurrentAuthSession.mTokenEscrow = token; - mCurrentAuthSession.mState = STATE_AUTH_PENDING_CONFIRM; - } - - // Notify SysUI that the biometric has been authenticated. SysUI already knows - // the implicit/explicit state and will react accordingly. - mStatusBarService.onBiometricAuthenticated(true); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } - } - - @Override - public void onAuthenticationFailed(int cookie, boolean requireConfirmation) - throws RemoteException { - try { - // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded - // after user dismissed/canceled dialog). - if (mCurrentAuthSession == null) { - Slog.e(TAG, "onAuthenticationFailed(): Auth session is null"); - return; - } - - mStatusBarService.onBiometricAuthenticated(false); - - // TODO: This logic will need to be updated if BP is multi-modal - if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) { - // Pause authentication. onBiometricAuthenticated(false) causes the - // dialog to show a "try again" button for passive modalities. - mCurrentAuthSession.mState = STATE_AUTH_PAUSED; - } - - mCurrentAuthSession.mClientReceiver.onAuthenticationFailed(); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } - } - - @Override - public void onError(int cookie, int error, String message) throws RemoteException { - Slog.d(TAG, "Error: " + error + " cookie: " + cookie); - // Errors can either be from the current auth session or the pending auth session. - // The pending auth session may receive errors such as ERROR_LOCKOUT before - // it becomes the current auth session. Similarly, the current auth session may - // receive errors such as ERROR_CANCELED while the pending auth session is preparing - // to be started. Thus we must match error messages with their cookies to be sure - // of their intended receivers. - try { - if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) { - if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { - mStatusBarService.onBiometricError(message); - if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { - mActivityTaskManager.unregisterTaskStackListener( - mTaskStackListener); - mCurrentAuthSession.mClientReceiver.onError(error, message); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; - mStatusBarService.hideBiometricDialog(); - } else { - // Send errors after the dialog is dismissed. - mHandler.postDelayed(() -> { - try { - if (mCurrentAuthSession != null) { - mActivityTaskManager.unregisterTaskStackListener( - mTaskStackListener); - mCurrentAuthSession.mClientReceiver.onError(error, - message); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; - } - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } - }, BiometricPrompt.HIDE_DIALOG_DELAY); - } - } else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) { - // In the "try again" state, we should forward canceled errors to - // the client and and clean up. - mCurrentAuthSession.mClientReceiver.onError(error, message); - mStatusBarService.onBiometricError(message); - mActivityTaskManager.unregisterTaskStackListener( - mTaskStackListener); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; - } else { - Slog.e(TAG, "Impossible session error state: " - + mCurrentAuthSession.mState); - } - } else if (mPendingAuthSession != null - && mPendingAuthSession.containsCookie(cookie)) { - if (mPendingAuthSession.mState == STATE_AUTH_CALLED) { - mPendingAuthSession.mClientReceiver.onError(error, message); - mPendingAuthSession.mState = STATE_AUTH_IDLE; - mPendingAuthSession = null; - } else { - Slog.e(TAG, "Impossible pending session error state: " - + mPendingAuthSession.mState); - } - } - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } - } - - @Override - public void onAcquired(int acquiredInfo, String message) throws RemoteException { - // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded - // after user dismissed/canceled dialog). - if (mCurrentAuthSession == null) { - Slog.e(TAG, "onAcquired(): Auth session is null"); - return; - } - - if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { - try { - mStatusBarService.onBiometricHelp(message); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } - } - } - - @Override - public void onDialogDismissed(int reason) throws RemoteException { - if (mCurrentAuthSession == null) { - Slog.e(TAG, "onDialogDismissed: " + reason + ", auth session null"); - return; - } - - logDialogDismissed(reason); - - if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) { - // Positive button is used by passive modalities as a "confirm" button, - // do not send to client - mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason); - // Cancel authentication. Skip the token/package check since we are cancelling - // from system server. The interface is permission protected so this is fine. - cancelInternal(null /* token */, null /* package */, false /* fromClient */); - } - if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) { - mCurrentAuthSession.mClientReceiver.onError( - BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED, - getContext().getString( - com.android.internal.R.string.biometric_error_user_canceled)); - } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) { - // Have the service send the token to KeyStore, and send onAuthenticated - // to the application - KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow); - mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); - } - mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; - } - - @Override - public void onTryAgainPressed() { - Slog.d(TAG, "onTryAgainPressed"); - // No need to check permission, since it can only be invoked by SystemUI - // (or system server itself). - mHandler.post(() -> { - authenticateInternal(mCurrentAuthSession.mToken, - mCurrentAuthSession.mSessionId, - mCurrentAuthSession.mUserId, - mCurrentAuthSession.mClientReceiver, - mCurrentAuthSession.mOpPackageName, - mCurrentAuthSession.mBundle, - mCurrentAuthSession.mCallingUid, - mCurrentAuthSession.mCallingPid, - mCurrentAuthSession.mCallingUserId, - mCurrentAuthSession.mModality); - }); - } - - private void logDialogDismissed(int reason) { - if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) { - // Explicit auth, authentication confirmed. - // Latency in this case is authenticated -> confirmed. <Biometric>Service - // should have the first half (first acquired -> authenticated). - final long latency = System.currentTimeMillis() - - mCurrentAuthSession.mAuthenticatedTimeMs; - - if (LoggableMonitor.DEBUG) { - Slog.v(LoggableMonitor.TAG, "Confirmed! Modality: " + statsModality() - + ", User: " + mCurrentAuthSession.mUserId - + ", IsCrypto: " + mCurrentAuthSession.isCrypto() - + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT - + ", RequireConfirmation: " - + mCurrentAuthSession.mRequireConfirmation - + ", State: " + StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED - + ", Latency: " + latency); - } - - StatsLog.write(StatsLog.BIOMETRIC_AUTHENTICATED, - statsModality(), - mCurrentAuthSession.mUserId, - mCurrentAuthSession.isCrypto(), - BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, - mCurrentAuthSession.mRequireConfirmation, - StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED, - latency); - } else { - int error = reason == BiometricPrompt.DISMISSED_REASON_NEGATIVE - ? BiometricConstants.BIOMETRIC_ERROR_NEGATIVE_BUTTON - : reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL - ? BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED - : 0; - if (LoggableMonitor.DEBUG) { - Slog.v(LoggableMonitor.TAG, "Dismissed! Modality: " + statsModality() - + ", User: " + mCurrentAuthSession.mUserId - + ", IsCrypto: " + mCurrentAuthSession.isCrypto() - + ", Action: " + BiometricsProtoEnums.ACTION_AUTHENTICATE - + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT - + ", Error: " + error); - } - // Auth canceled - StatsLog.write(StatsLog.BIOMETRIC_ERROR_OCCURRED, - statsModality(), - mCurrentAuthSession.mUserId, - mCurrentAuthSession.isCrypto(), - BiometricsProtoEnums.ACTION_AUTHENTICATE, - BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, - error, - 0 /* vendorCode */); - } - } - - private int statsModality() { - int modality = 0; - if (mCurrentAuthSession == null) { - return BiometricsProtoEnums.MODALITY_UNKNOWN; - } - if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FINGERPRINT) - != 0) { - modality |= BiometricsProtoEnums.MODALITY_FINGERPRINT; - } - if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_IRIS) != 0) { - modality |= BiometricsProtoEnums.MODALITY_IRIS; - } - if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FACE) != 0) { - modality |= BiometricsProtoEnums.MODALITY_FACE; - } - return modality; - } - }; - @Override // Binder call public void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId) { checkInternalPermission(); - Iterator it = mPendingAuthSession.mModalitiesWaiting.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<Integer, Integer> pair = (Map.Entry) it.next(); - if (pair.getValue() == cookie) { - mPendingAuthSession.mModalitiesMatched.put(pair.getKey(), pair.getValue()); - mPendingAuthSession.mModalitiesWaiting.remove(pair.getKey()); - Slog.d(TAG, "Matched cookie: " + cookie + ", " - + mPendingAuthSession.mModalitiesWaiting.size() + " remaining"); - break; - } - } - - if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) { - final boolean continuing = mCurrentAuthSession != null && - (mCurrentAuthSession.mState == STATE_AUTH_PAUSED); - - mCurrentAuthSession = mPendingAuthSession; - mPendingAuthSession = null; - - mCurrentAuthSession.mState = STATE_AUTH_STARTED; - try { - int modality = TYPE_NONE; - it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<Integer, Integer> pair = (Map.Entry) it.next(); - if (pair.getKey() == TYPE_FINGERPRINT) { - mFingerprintService.startPreparedClient(pair.getValue()); - } else if (pair.getKey() == TYPE_IRIS) { - Slog.e(TAG, "Iris unsupported"); - } else if (pair.getKey() == TYPE_FACE) { - mFaceService.startPreparedClient(pair.getValue()); - } else { - Slog.e(TAG, "Unknown modality: " + pair.getKey()); - } - modality |= pair.getKey(); - } - - if (!continuing) { - mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle, - mInternalReceiver, modality, requireConfirmation, userId); - mActivityTaskManager.registerTaskStackListener(mTaskStackListener); - } - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } - } + SomeArgs args = SomeArgs.obtain(); + args.argi1 = cookie; + args.arg1 = requireConfirmation; + args.argi2 = userId; + mHandler.obtainMessage(MSG_ON_READY_FOR_AUTHENTICATION, args).sendToTarget(); } @Override // Binder call @@ -754,25 +577,23 @@ public class BiometricService extends SystemService { checkInternalPermission(); // Set the default title if necessary try { - if (useDefaultTitle) { - final List<ActivityManager.RunningAppProcessInfo> procs = - ActivityManager.getService().getRunningAppProcesses(); - for (int i = 0; i < procs.size(); i++) { - final ActivityManager.RunningAppProcessInfo info = procs.get(i); - if (info.uid == callingUid - && info.importance == IMPORTANCE_FOREGROUND) { - PackageManager pm = getContext().getPackageManager(); - final CharSequence label = pm.getApplicationLabel( - pm.getApplicationInfo(info.processName, - PackageManager.GET_META_DATA)); - final String title = getContext() - .getString(R.string.biometric_dialog_default_title, label); - if (TextUtils.isEmpty( - bundle.getCharSequence(BiometricPrompt.KEY_TITLE))) { - bundle.putCharSequence(BiometricPrompt.KEY_TITLE, title); - } - break; + final List<ActivityManager.RunningAppProcessInfo> procs = + ActivityManager.getService().getRunningAppProcesses(); + for (int i = 0; i < procs.size(); i++) { + final ActivityManager.RunningAppProcessInfo info = procs.get(i); + if (info.uid == callingUid + && info.importance == IMPORTANCE_FOREGROUND) { + PackageManager pm = getContext().getPackageManager(); + final CharSequence label = pm.getApplicationLabel( + pm.getApplicationInfo(info.processName, + PackageManager.GET_META_DATA)); + final String title = getContext() + .getString(R.string.biometric_dialog_default_title, label); + if (TextUtils.isEmpty( + bundle.getCharSequence(BiometricPrompt.KEY_TITLE))) { + bundle.putCharSequence(BiometricPrompt.KEY_TITLE, title); } + break; } } } catch (RemoteException e) { @@ -792,7 +613,8 @@ public class BiometricService extends SystemService { KeyguardManager.class); if (!kgm.isDeviceSecure()) { try { - receiver.onError(BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL, + receiver.onError( + BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL, getContext().getString( R.string.biometric_error_device_not_secured)); } catch (RemoteException e) { @@ -811,160 +633,63 @@ public class BiometricService extends SystemService { return; } - mHandler.post(() -> { - final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId); - final int modality = result.first; - final int error = result.second; - - // Check for errors, notify callback, and return - if (error != BiometricConstants.BIOMETRIC_SUCCESS) { - try { - final String hardwareUnavailable = - getContext().getString(R.string.biometric_error_hw_unavailable); - switch (error) { - case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT: - receiver.onError(error, hardwareUnavailable); - break; - case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE: - receiver.onError(error, hardwareUnavailable); - break; - case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS: - receiver.onError(error, - getErrorString(modality, error, 0 /* vendorCode */)); - break; - default: - Slog.e(TAG, "Unhandled error"); - break; - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to send error", e); - } - return; - } - - mCurrentModality = modality; - - // Start preparing for authentication. Authentication starts when - // all modalities requested have invoked onReadyForAuthentication. - authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, - callingUid, callingPid, callingUserId, modality); - }); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = token; + args.arg2 = sessionId; + args.argi1 = userId; + args.arg3 = receiver; + args.arg4 = opPackageName; + args.arg5 = bundle; + args.argi2 = callingUid; + args.argi3 = callingPid; + args.argi4 = callingUserId; + + mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget(); } @Override // Binder call public void onConfirmDeviceCredentialSuccess() { checkInternalPermission(); - if (mConfirmDeviceCredentialReceiver == null) { - Slog.w(TAG, "onCDCASuccess null!"); - return; - } - try { - mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded(); - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException", e); - } - mConfirmDeviceCredentialReceiver = null; + mHandler.post(() -> { + if (mConfirmDeviceCredentialReceiver == null) { + Slog.w(TAG, "onCDCASuccess null!"); + return; + } + try { + mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded(); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + mConfirmDeviceCredentialReceiver = null; + }); } @Override // Binder call public void onConfirmDeviceCredentialError(int error, String message) { checkInternalPermission(); - if (mConfirmDeviceCredentialReceiver == null) { - Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message); - return; - } - try { - mConfirmDeviceCredentialReceiver.onError(error, message); - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException", e); - } - mConfirmDeviceCredentialReceiver = null; - } - - /** - * authenticate() (above) which is called from BiometricPrompt determines which - * modality/modalities to start authenticating with. authenticateInternal() should only be - * used for: - * 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is, - * invoked, shortly after which BiometricPrompt is shown and authentication starts - * 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown - * and the user has pressed "try again" - */ - private void authenticateInternal(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, - int callingUid, int callingPid, int callingUserId, int modality) { - try { - boolean requireConfirmation = bundle.getBoolean( - BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */); - if ((modality & TYPE_FACE) != 0) { - // Check if the user has forced confirmation to be required in Settings. - requireConfirmation = requireConfirmation - || mSettingObserver.getFaceAlwaysRequireConfirmation(); - } - // Generate random cookies to pass to the services that should prepare to start - // authenticating. Store the cookie here and wait for all services to "ack" - // with the cookie. Once all cookies are received, we can show the prompt - // and let the services start authenticating. The cookie should be non-zero. - final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; - Slog.d(TAG, "Creating auth session. Modality: " + modality - + ", cookie: " + cookie); - final HashMap<Integer, Integer> authenticators = new HashMap<>(); - authenticators.put(modality, cookie); - mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId, - receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, - modality, requireConfirmation); - mPendingAuthSession.mState = STATE_AUTH_CALLED; - // No polymorphism :( - if ((modality & TYPE_FINGERPRINT) != 0) { - mFingerprintService.prepareForAuthentication(token, sessionId, userId, - mInternalReceiver, opPackageName, cookie, - callingUid, callingPid, callingUserId); - } - if ((modality & TYPE_IRIS) != 0) { - Slog.w(TAG, "Iris unsupported"); + mHandler.post(() -> { + if (mConfirmDeviceCredentialReceiver == null) { + Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message); + return; } - if ((modality & TYPE_FACE) != 0) { - mFaceService.prepareForAuthentication(requireConfirmation, - token, sessionId, userId, mInternalReceiver, opPackageName, - cookie, callingUid, callingPid, callingUserId); + try { + mConfirmDeviceCredentialReceiver.onError(error, message); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to start authentication", e); - } + mConfirmDeviceCredentialReceiver = null; + }); } @Override // Binder call public void cancelAuthentication(IBinder token, String opPackageName) throws RemoteException { checkPermission(); - if (token == null || opPackageName == null) { - Slog.e(TAG, "Unable to cancel, one or more null arguments"); - return; - } - - // 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. - if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { - mHandler.post(() -> { - try { - // Send error to client - mCurrentAuthSession.mClientReceiver.onError( - BiometricConstants.BIOMETRIC_ERROR_CANCELED, - getContext().getString( - com.android.internal.R.string.biometric_error_user_canceled) - ); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; - mStatusBarService.hideBiometricDialog(); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } - }); - } else { - cancelInternal(token, opPackageName, true /* fromClient */); - } + SomeArgs args = SomeArgs.obtain(); + args.arg1 = token; + args.arg2 = opPackageName; + mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget(); } @Override // Binder call @@ -1027,31 +752,6 @@ public class BiometricService extends SystemService { Binder.restoreCallingIdentity(ident); } } - - void cancelInternal(IBinder token, String opPackageName, boolean fromClient) { - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - final int callingUserId = UserHandle.getCallingUserId(); - mHandler.post(() -> { - try { - // TODO: For multiple modalities, send a single ERROR_CANCELED only when all - // drivers have canceled authentication. - if ((mCurrentModality & TYPE_FINGERPRINT) != 0) { - mFingerprintService.cancelAuthenticationFromService(token, opPackageName, - callingUid, callingPid, callingUserId, fromClient); - } - if ((mCurrentModality & TYPE_IRIS) != 0) { - Slog.w(TAG, "Iris unsupported"); - } - if ((mCurrentModality & TYPE_FACE) != 0) { - mFaceService.cancelAuthenticationFromService(token, opPackageName, - callingUid, callingPid, callingUserId, fromClient); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to cancel authentication"); - } - }); - } } private void checkAppOp(String opPackageName, int callingUid) { @@ -1088,7 +788,6 @@ public class BiometricService extends SystemService { super(context); mAppOps = context.getSystemService(AppOpsManager.class); - mHandler = new Handler(Looper.getMainLooper()); mEnabledOnKeyguardCallbacks = new ArrayList<>(); mSettingObserver = new SettingObserver(mHandler); @@ -1123,6 +822,10 @@ public class BiometricService extends SystemService { ServiceManager.getService(Context.FACE_SERVICE)); } + mActivityTaskManager = ActivityTaskManager.getService(); + mStatusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + // Cache the authenticators for (int i = 0; i < FEATURE_ID.length; i++) { if (hasFeature(FEATURE_ID[i])) { @@ -1259,4 +962,491 @@ public class BiometricService extends SystemService { return false; } } + + private void logDialogDismissed(int reason) { + if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) { + // Explicit auth, authentication confirmed. + // Latency in this case is authenticated -> confirmed. <Biometric>Service + // should have the first half (first acquired -> authenticated). + final long latency = System.currentTimeMillis() + - mCurrentAuthSession.mAuthenticatedTimeMs; + + if (LoggableMonitor.DEBUG) { + Slog.v(LoggableMonitor.TAG, "Confirmed! Modality: " + statsModality() + + ", User: " + mCurrentAuthSession.mUserId + + ", IsCrypto: " + mCurrentAuthSession.isCrypto() + + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT + + ", RequireConfirmation: " + + mCurrentAuthSession.mRequireConfirmation + + ", State: " + StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED + + ", Latency: " + latency); + } + + StatsLog.write(StatsLog.BIOMETRIC_AUTHENTICATED, + statsModality(), + mCurrentAuthSession.mUserId, + mCurrentAuthSession.isCrypto(), + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, + mCurrentAuthSession.mRequireConfirmation, + StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED, + latency); + } else { + int error = reason == BiometricPrompt.DISMISSED_REASON_NEGATIVE + ? BiometricConstants.BIOMETRIC_ERROR_NEGATIVE_BUTTON + : reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL + ? BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED + : 0; + if (LoggableMonitor.DEBUG) { + Slog.v(LoggableMonitor.TAG, "Dismissed! Modality: " + statsModality() + + ", User: " + mCurrentAuthSession.mUserId + + ", IsCrypto: " + mCurrentAuthSession.isCrypto() + + ", Action: " + BiometricsProtoEnums.ACTION_AUTHENTICATE + + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT + + ", Error: " + error); + } + // Auth canceled + StatsLog.write(StatsLog.BIOMETRIC_ERROR_OCCURRED, + statsModality(), + mCurrentAuthSession.mUserId, + mCurrentAuthSession.isCrypto(), + BiometricsProtoEnums.ACTION_AUTHENTICATE, + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, + error, + 0 /* vendorCode */); + } + } + + private int statsModality() { + int modality = 0; + if (mCurrentAuthSession == null) { + return BiometricsProtoEnums.MODALITY_UNKNOWN; + } + if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FINGERPRINT) + != 0) { + modality |= BiometricsProtoEnums.MODALITY_FINGERPRINT; + } + if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_IRIS) != 0) { + modality |= BiometricsProtoEnums.MODALITY_IRIS; + } + if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FACE) != 0) { + modality |= BiometricsProtoEnums.MODALITY_FACE; + } + return modality; + } + + private void handleTaskStackChanged() { + try { + final List<ActivityManager.RunningTaskInfo> runningTasks = + mActivityTaskManager.getTasks(1); + if (!runningTasks.isEmpty()) { + final String topPackage = runningTasks.get(0).topActivity.getPackageName(); + if (mCurrentAuthSession != null + && !topPackage.contentEquals(mCurrentAuthSession.mOpPackageName)) { + mStatusBarService.hideBiometricDialog(); + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + mCurrentAuthSession.mClientReceiver.onError( + BiometricConstants.BIOMETRIC_ERROR_CANCELED, + getContext().getString( + com.android.internal.R.string.biometric_error_canceled) + ); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to get running tasks", e); + } + } + + private void handleAuthenticationSucceeded(boolean requireConfirmation, byte[] token) { + + try { + // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded + // after user dismissed/canceled dialog). + if (mCurrentAuthSession == null) { + Slog.e(TAG, "onAuthenticationSucceeded(): Auth session is null"); + return; + } + + if (!requireConfirmation) { + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + KeyStore.getInstance().addAuthToken(token); + mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } else { + mCurrentAuthSession.mAuthenticatedTimeMs = System.currentTimeMillis(); + // Store the auth token and submit it to keystore after the confirmation + // button has been pressed. + mCurrentAuthSession.mTokenEscrow = token; + mCurrentAuthSession.mState = STATE_AUTH_PENDING_CONFIRM; + } + + // Notify SysUI that the biometric has been authenticated. SysUI already knows + // the implicit/explicit state and will react accordingly. + mStatusBarService.onBiometricAuthenticated(true); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + private void handleAuthenticationFailed(int cookie, boolean requireConfirmation) { + try { + // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded + // after user dismissed/canceled dialog). + if (mCurrentAuthSession == null) { + Slog.e(TAG, "onAuthenticationFailed(): Auth session is null"); + return; + } + + mStatusBarService.onBiometricAuthenticated(false); + + // TODO: This logic will need to be updated if BP is multi-modal + if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) { + // Pause authentication. onBiometricAuthenticated(false) causes the + // dialog to show a "try again" button for passive modalities. + mCurrentAuthSession.mState = STATE_AUTH_PAUSED; + } + + mCurrentAuthSession.mClientReceiver.onAuthenticationFailed(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + private void handleOnError(int cookie, int error, String message) { + Slog.d(TAG, "Error: " + error + " cookie: " + cookie); + // Errors can either be from the current auth session or the pending auth session. + // The pending auth session may receive errors such as ERROR_LOCKOUT before + // it becomes the current auth session. Similarly, the current auth session may + // receive errors such as ERROR_CANCELED while the pending auth session is preparing + // to be started. Thus we must match error messages with their cookies to be sure + // of their intended receivers. + try { + if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) { + if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { + mStatusBarService.onBiometricError(message); + if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { + mActivityTaskManager.unregisterTaskStackListener( + mTaskStackListener); + mCurrentAuthSession.mClientReceiver.onError(error, message); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + mStatusBarService.hideBiometricDialog(); + } else { + // Send errors after the dialog is dismissed. + mHandler.postDelayed(() -> { + try { + if (mCurrentAuthSession != null) { + mActivityTaskManager.unregisterTaskStackListener( + mTaskStackListener); + mCurrentAuthSession.mClientReceiver.onError(error, + message); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + }, BiometricPrompt.HIDE_DIALOG_DELAY); + } + } else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) { + // In the "try again" state, we should forward canceled errors to + // the client and and clean up. + mCurrentAuthSession.mClientReceiver.onError(error, message); + mStatusBarService.onBiometricError(message); + mActivityTaskManager.unregisterTaskStackListener( + mTaskStackListener); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } else { + Slog.e(TAG, "Impossible session error state: " + + mCurrentAuthSession.mState); + } + } else if (mPendingAuthSession != null + && mPendingAuthSession.containsCookie(cookie)) { + if (mPendingAuthSession.mState == STATE_AUTH_CALLED) { + mPendingAuthSession.mClientReceiver.onError(error, message); + mPendingAuthSession.mState = STATE_AUTH_IDLE; + mPendingAuthSession = null; + } else { + Slog.e(TAG, "Impossible pending session error state: " + + mPendingAuthSession.mState); + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + private void handleOnAcquired(int acquiredInfo, String message) { + // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded + // after user dismissed/canceled dialog). + if (mCurrentAuthSession == null) { + Slog.e(TAG, "onAcquired(): Auth session is null"); + return; + } + + if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { + try { + mStatusBarService.onBiometricHelp(message); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + } + + private void handleOnDismissed(int reason) { + if (mCurrentAuthSession == null) { + Slog.e(TAG, "onDialogDismissed: " + reason + ", auth session null"); + return; + } + + logDialogDismissed(reason); + + try { + if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) { + // Positive button is used by passive modalities as a "confirm" button, + // do not send to client + mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason); + // Cancel authentication. Skip the token/package check since we are cancelling + // from system server. The interface is permission protected so this is fine. + cancelInternal(null /* token */, null /* package */, false /* fromClient */); + } + if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) { + mCurrentAuthSession.mClientReceiver.onError( + BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED, + getContext().getString( + com.android.internal.R.string.biometric_error_user_canceled)); + } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) { + // Have the service send the token to KeyStore, and send onAuthenticated + // to the application + KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow); + mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); + } + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + private void handleOnTryAgainPressed() { + Slog.d(TAG, "onTryAgainPressed"); + // No need to check permission, since it can only be invoked by SystemUI + // (or system server itself). + authenticateInternal(mCurrentAuthSession.mToken, + mCurrentAuthSession.mSessionId, + mCurrentAuthSession.mUserId, + mCurrentAuthSession.mClientReceiver, + mCurrentAuthSession.mOpPackageName, + mCurrentAuthSession.mBundle, + mCurrentAuthSession.mCallingUid, + mCurrentAuthSession.mCallingPid, + mCurrentAuthSession.mCallingUserId, + mCurrentAuthSession.mModality); + } + + private void handleOnReadyForAuthentication(int cookie, boolean requireConfirmation, + int userId) { + Iterator it = mPendingAuthSession.mModalitiesWaiting.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<Integer, Integer> pair = (Map.Entry) it.next(); + if (pair.getValue() == cookie) { + mPendingAuthSession.mModalitiesMatched.put(pair.getKey(), pair.getValue()); + mPendingAuthSession.mModalitiesWaiting.remove(pair.getKey()); + Slog.d(TAG, "Matched cookie: " + cookie + ", " + + mPendingAuthSession.mModalitiesWaiting.size() + " remaining"); + break; + } + } + + if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) { + final boolean continuing = mCurrentAuthSession != null + && mCurrentAuthSession.mState == STATE_AUTH_PAUSED; + + mCurrentAuthSession = mPendingAuthSession; + mPendingAuthSession = null; + + mCurrentAuthSession.mState = STATE_AUTH_STARTED; + try { + int modality = TYPE_NONE; + it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<Integer, Integer> pair = (Map.Entry) it.next(); + if (pair.getKey() == TYPE_FINGERPRINT) { + mFingerprintService.startPreparedClient(pair.getValue()); + } else if (pair.getKey() == TYPE_IRIS) { + Slog.e(TAG, "Iris unsupported"); + } else if (pair.getKey() == TYPE_FACE) { + mFaceService.startPreparedClient(pair.getValue()); + } else { + Slog.e(TAG, "Unknown modality: " + pair.getKey()); + } + modality |= pair.getKey(); + } + + if (!continuing) { + mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle, + mInternalReceiver, modality, requireConfirmation, userId); + mActivityTaskManager.registerTaskStackListener(mTaskStackListener); + } + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + } + + private void handleAuthenticate(IBinder token, long sessionId, int userId, + IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, + int callingUid, int callingPid, int callingUserId) { + + mHandler.post(() -> { + final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId); + final int modality = result.first; + final int error = result.second; + + // Check for errors, notify callback, and return + if (error != BiometricConstants.BIOMETRIC_SUCCESS) { + try { + final String hardwareUnavailable = + getContext().getString(R.string.biometric_error_hw_unavailable); + switch (error) { + case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT: + receiver.onError(error, hardwareUnavailable); + break; + case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE: + receiver.onError(error, hardwareUnavailable); + break; + case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS: + receiver.onError(error, + getErrorString(modality, error, 0 /* vendorCode */)); + break; + default: + Slog.e(TAG, "Unhandled error"); + break; + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to send error", e); + } + return; + } + + mCurrentModality = modality; + + // Start preparing for authentication. Authentication starts when + // all modalities requested have invoked onReadyForAuthentication. + authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, + callingUid, callingPid, callingUserId, modality); + }); + } + + /** + * authenticate() (above) which is called from BiometricPrompt determines which + * modality/modalities to start authenticating with. authenticateInternal() should only be + * used for: + * 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is, + * invoked, shortly after which BiometricPrompt is shown and authentication starts + * 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown + * and the user has pressed "try again" + */ + private void authenticateInternal(IBinder token, long sessionId, int userId, + IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, + int callingUid, int callingPid, int callingUserId, int modality) { + try { + boolean requireConfirmation = bundle.getBoolean( + BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */); + if ((modality & TYPE_FACE) != 0) { + // Check if the user has forced confirmation to be required in Settings. + requireConfirmation = requireConfirmation + || mSettingObserver.getFaceAlwaysRequireConfirmation(); + } + // Generate random cookies to pass to the services that should prepare to start + // authenticating. Store the cookie here and wait for all services to "ack" + // with the cookie. Once all cookies are received, we can show the prompt + // and let the services start authenticating. The cookie should be non-zero. + final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; + Slog.d(TAG, "Creating auth session. Modality: " + modality + + ", cookie: " + cookie); + final HashMap<Integer, Integer> authenticators = new HashMap<>(); + authenticators.put(modality, cookie); + mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId, + receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, + modality, requireConfirmation); + mPendingAuthSession.mState = STATE_AUTH_CALLED; + // No polymorphism :( + if ((modality & TYPE_FINGERPRINT) != 0) { + mFingerprintService.prepareForAuthentication(token, sessionId, userId, + mInternalReceiver, opPackageName, cookie, + callingUid, callingPid, callingUserId); + } + if ((modality & TYPE_IRIS) != 0) { + Slog.w(TAG, "Iris unsupported"); + } + if ((modality & TYPE_FACE) != 0) { + mFaceService.prepareForAuthentication(requireConfirmation, + token, sessionId, userId, mInternalReceiver, opPackageName, + cookie, callingUid, callingPid, callingUserId); + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to start authentication", e); + } + } + + private void handleCancelAuthentication(IBinder token, String opPackageName) { + if (token == null || opPackageName == null) { + Slog.e(TAG, "Unable to cancel, one or more null arguments"); + return; + } + + // 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. + if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { + try { + // Send error to client + mCurrentAuthSession.mClientReceiver.onError( + BiometricConstants.BIOMETRIC_ERROR_CANCELED, + getContext().getString( + com.android.internal.R.string.biometric_error_user_canceled) + ); + + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + mStatusBarService.hideBiometricDialog(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } else { + cancelInternal(token, opPackageName, true /* fromClient */); + } + } + + + void cancelInternal(IBinder token, String opPackageName, boolean fromClient) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int callingUserId = UserHandle.getCallingUserId(); + mHandler.post(() -> { + try { + // TODO: For multiple modalities, send a single ERROR_CANCELED only when all + // drivers have canceled authentication. + if ((mCurrentModality & TYPE_FINGERPRINT) != 0) { + mFingerprintService.cancelAuthenticationFromService(token, opPackageName, + callingUid, callingPid, callingUserId, fromClient); + } + if ((mCurrentModality & TYPE_IRIS) != 0) { + Slog.w(TAG, "Iris unsupported"); + } + if ((mCurrentModality & TYPE_FACE) != 0) { + mFaceService.cancelAuthenticationFromService(token, opPackageName, + callingUid, callingPid, callingUserId, fromClient); + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to cancel authentication"); + } + }); + } + } |