/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.TaskStackListener; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PointF; import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManager; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintStateListener; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; import android.hardware.fingerprint.IUdfpsHbmListener; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.util.Log; import android.util.SparseBooleanArray; import android.view.MotionEvent; import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.systemui.SystemUI; import com.android.systemui.assist.ui.DisplayUtils; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.util.concurrency.Execution; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.inject.Inject; import javax.inject.Provider; import kotlin.Unit; /** * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the * appropriate biometric UI (e.g. BiometricDialogView). * * Also coordinates biometric-related things, such as UDFPS, with * {@link com.android.keyguard.KeyguardUpdateMonitor} */ @SysUISingleton public class AuthController extends SystemUI implements CommandQueue.Callbacks, AuthDialogCallback, DozeReceiver { private static final String TAG = "AuthController"; private static final boolean DEBUG = true; private static final int SENSOR_PRIVACY_DELAY = 500; private final Handler mHandler; private final Execution mExecution; private final CommandQueue mCommandQueue; private final ActivityTaskManager mActivityTaskManager; @Nullable private final FingerprintManager mFingerprintManager; @Nullable private final FaceManager mFaceManager; private final Provider mUdfpsControllerFactory; private final Provider mSidefpsControllerFactory; @Nullable private final PointF mFaceAuthSensorLocation; @Nullable private PointF mFingerprintLocation; private final Set mCallbacks = new HashSet<>(); // TODO: These should just be saved from onSaveState private SomeArgs mCurrentDialogArgs; @VisibleForTesting AuthDialog mCurrentDialog; @NonNull private final WindowManager mWindowManager; @Nullable private UdfpsController mUdfpsController; @Nullable private IUdfpsHbmListener mUdfpsHbmListener; @Nullable private SidefpsController mSidefpsController; @VisibleForTesting TaskStackListener mTaskStackListener; @VisibleForTesting IBiometricSysuiReceiver mReceiver; @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener; @Nullable private final List mFaceProps; @Nullable private List mFpProps; @Nullable private List mUdfpsProps; @Nullable private List mSidefpsProps; @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; private SensorPrivacyManager mSensorPrivacyManager; private class BiometricTaskStackListener extends TaskStackListener { @Override public void onTaskStackChanged() { mHandler.post(AuthController.this::handleTaskStackChanged); } } private final IFingerprintAuthenticatorsRegisteredCallback mFingerprintAuthenticatorsRegisteredCallback = new IFingerprintAuthenticatorsRegisteredCallback.Stub() { @Override public void onAllAuthenticatorsRegistered( List sensors) { mHandler.post(() -> handleAllAuthenticatorsRegistered(sensors)); } }; private final FingerprintStateListener mFingerprintStateListener = new FingerprintStateListener() { @Override public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { mHandler.post( () -> handleEnrollmentsChanged(userId, sensorId, hasEnrollments)); } }; @VisibleForTesting final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mCurrentDialog != null && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { Log.w(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received"); mCurrentDialog.dismissWithoutCallback(true /* animate */); mCurrentDialog = null; mOrientationListener.disable(); try { if (mReceiver != null) { mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */); mReceiver = null; } } catch (RemoteException e) { Log.e(TAG, "Remote exception", e); } } } }; private void handleTaskStackChanged() { mExecution.assertIsMainThread(); if (mCurrentDialog != null) { try { final String clientPackage = mCurrentDialog.getOpPackageName(); Log.w(TAG, "Task stack changed, current client: " + clientPackage); final List runningTasks = mActivityTaskManager.getTasks(1); if (!runningTasks.isEmpty()) { final String topPackage = runningTasks.get(0).topActivity.getPackageName(); if (!topPackage.contentEquals(clientPackage) && !Utils.isSystem(mContext, clientPackage)) { Log.w(TAG, "Evicting client due to: " + topPackage); mCurrentDialog.dismissWithoutCallback(true /* animate */); mCurrentDialog = null; mOrientationListener.disable(); if (mReceiver != null) { mReceiver.onDialogDismissed( BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */); mReceiver = null; } } } } catch (RemoteException e) { Log.e(TAG, "Remote exception", e); } } } private void handleAllAuthenticatorsRegistered( List sensors) { mExecution.assertIsMainThread(); if (DEBUG) { Log.d(TAG, "handleAllAuthenticatorsRegistered | sensors: " + Arrays.toString( sensors.toArray())); } mFpProps = sensors; List udfpsProps = new ArrayList<>(); List sidefpsProps = new ArrayList<>(); for (FingerprintSensorPropertiesInternal props : mFpProps) { if (props.isAnyUdfpsType()) { udfpsProps.add(props); } if (props.isAnySidefpsType()) { sidefpsProps.add(props); } } mUdfpsProps = !udfpsProps.isEmpty() ? udfpsProps : null; if (mUdfpsProps != null) { mUdfpsController = mUdfpsControllerFactory.get(); } mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null; if (mSidefpsProps != null) { mSidefpsController = mSidefpsControllerFactory.get(); } for (Callback cb : mCallbacks) { cb.onAllAuthenticatorsRegistered(); } mFingerprintManager.registerFingerprintStateListener(mFingerprintStateListener); } private void handleEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { mExecution.assertIsMainThread(); Log.d(TAG, "handleEnrollmentsChanged, userId: " + userId + ", sensorId: " + sensorId + ", hasEnrollments: " + hasEnrollments); if (mUdfpsProps == null) { Log.d(TAG, "handleEnrollmentsChanged, mUdfpsProps is null"); } else { for (FingerprintSensorPropertiesInternal prop : mUdfpsProps) { if (prop.sensorId == sensorId) { mUdfpsEnrolledForUser.put(userId, hasEnrollments); } } } for (Callback cb : mCallbacks) { cb.onEnrollmentsChanged(); } } /** * Adds a callback. See {@link Callback}. */ public void addCallback(@NonNull Callback callback) { mCallbacks.add(callback); } /** * Removes a callback. See {@link Callback}. */ public void removeCallback(@NonNull Callback callback) { mCallbacks.remove(callback); } @Override public void dozeTimeTick() { if (mUdfpsController != null) { mUdfpsController.dozeTimeTick(); } } @Override public void onTryAgainPressed() { if (mReceiver == null) { Log.e(TAG, "onTryAgainPressed: Receiver is null"); return; } try { mReceiver.onTryAgainPressed(); } catch (RemoteException e) { Log.e(TAG, "RemoteException when handling try again", e); } } @Override public void onDeviceCredentialPressed() { if (mReceiver == null) { Log.e(TAG, "onDeviceCredentialPressed: Receiver is null"); return; } try { mReceiver.onDeviceCredentialPressed(); } catch (RemoteException e) { Log.e(TAG, "RemoteException when handling credential button", e); } } @Override public void onSystemEvent(int event) { if (mReceiver == null) { Log.e(TAG, "onSystemEvent(" + event + "): Receiver is null"); return; } try { mReceiver.onSystemEvent(event); } catch (RemoteException e) { Log.e(TAG, "RemoteException when sending system event", e); } } @Override public void onDialogAnimatedIn() { if (mReceiver == null) { Log.e(TAG, "onDialogAnimatedIn: Receiver is null"); return; } try { mReceiver.onDialogAnimatedIn(); } catch (RemoteException e) { Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e); } } @Override public void onStartFingerprintNow() { if (mReceiver == null) { Log.e(TAG, "onStartUdfpsNow: Receiver is null"); return; } try { mReceiver.onStartFingerprintNow(); } catch (RemoteException e) { Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e); } } @Override public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) { switch (reason) { case AuthDialogCallback.DISMISSED_USER_CANCELED: sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, credentialAttestation); break; case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE: sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE, credentialAttestation); break; case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE: sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, credentialAttestation); break; case AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED: sendResultAndCleanUp( BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED, credentialAttestation); break; case AuthDialogCallback.DISMISSED_ERROR: sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR, credentialAttestation); break; case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER: sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED, credentialAttestation); break; case AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED: sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED, credentialAttestation); break; default: Log.e(TAG, "Unhandled reason: " + reason); break; } } /** * @return where the UDFPS exists on the screen in pixels in portrait mode. */ @Nullable public PointF getUdfpsSensorLocation() { if (mUdfpsController == null) { return null; } return new PointF(mUdfpsController.getSensorLocation().centerX(), mUdfpsController.getSensorLocation().centerY()); } /** * @return where the fingerprint sensor exists in pixels in portrait mode. devices without an * overridden value will use the default value even if they don't have a fingerprint sensor */ @Nullable public PointF getFingerprintSensorLocation() { if (getUdfpsSensorLocation() != null) { return getUdfpsSensorLocation(); } return mFingerprintLocation; } /** * @return where the face authentication sensor exists relative to the screen in pixels in * portrait mode. */ @Nullable public PointF getFaceAuthSensorLocation() { if (mFaceProps == null || mFaceAuthSensorLocation == null) { return null; } return new PointF(mFaceAuthSensorLocation.x, mFaceAuthSensorLocation.y); } /** * Requests fingerprint scan. * * @param screenX X position of long press * @param screenY Y position of long press * @param major length of the major axis. See {@link MotionEvent#AXIS_TOOL_MAJOR}. * @param minor length of the minor axis. See {@link MotionEvent#AXIS_TOOL_MINOR}. */ public void onAodInterrupt(int screenX, int screenY, float major, float minor) { if (mUdfpsController == null) { return; } mUdfpsController.onAodInterrupt(screenX, screenY, major, minor); } /** * Cancel a fingerprint scan manually. This will get rid of the white circle on the udfps * sensor area even if the user hasn't explicitly lifted their finger yet. */ public void onCancelUdfps() { if (mUdfpsController == null) { return; } mUdfpsController.onCancelUdfps(); } private void sendResultAndCleanUp(@DismissedReason int reason, @Nullable byte[] credentialAttestation) { if (mReceiver == null) { Log.e(TAG, "sendResultAndCleanUp: Receiver is null"); return; } try { mReceiver.onDialogDismissed(reason, credentialAttestation); } catch (RemoteException e) { Log.w(TAG, "Remote exception", e); } onDialogDismissed(reason); } @Inject public AuthController(Context context, Execution execution, CommandQueue commandQueue, ActivityTaskManager activityTaskManager, @NonNull WindowManager windowManager, @Nullable FingerprintManager fingerprintManager, @Nullable FaceManager faceManager, Provider udfpsControllerFactory, Provider sidefpsControllerFactory, @NonNull DisplayManager displayManager, @Main Handler handler) { super(context); mExecution = execution; mHandler = handler; mCommandQueue = commandQueue; mActivityTaskManager = activityTaskManager; mFingerprintManager = fingerprintManager; mFaceManager = faceManager; mUdfpsControllerFactory = udfpsControllerFactory; mSidefpsControllerFactory = sidefpsControllerFactory; mWindowManager = windowManager; mUdfpsEnrolledForUser = new SparseBooleanArray(); mOrientationListener = new BiometricDisplayListener( context, displayManager, mHandler, BiometricDisplayListener.SensorType.Generic.INSTANCE, () -> { onOrientationChanged(); return Unit.INSTANCE; }); mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null; int[] faceAuthLocation = context.getResources().getIntArray( com.android.systemui.R.array.config_face_auth_props); if (faceAuthLocation == null || faceAuthLocation.length < 2) { mFaceAuthSensorLocation = null; } else { mFaceAuthSensorLocation = new PointF( (float) faceAuthLocation[0], (float) faceAuthLocation[1]); } updateFingerprintLocation(); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.registerReceiver(mBroadcastReceiver, filter); mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } private void updateFingerprintLocation() { int xLocation = DisplayUtils.getWidth(mContext) / 2; try { xLocation = mContext.getResources().getDimensionPixelSize( com.android.systemui.R.dimen .physical_fingerprint_sensor_center_screen_location_x); } catch (Resources.NotFoundException e) { } int yLocation = mContext.getResources().getDimensionPixelSize( com.android.systemui.R.dimen.physical_fingerprint_sensor_center_screen_location_y); mFingerprintLocation = new PointF( xLocation, yLocation); } @SuppressWarnings("deprecation") @Override public void start() { mCommandQueue.addCallback(this); if (mFingerprintManager != null) { mFingerprintManager.addAuthenticatorsRegisteredCallback( mFingerprintAuthenticatorsRegisteredCallback); } mTaskStackListener = new BiometricTaskStackListener(); mActivityTaskManager.registerTaskStackListener(mTaskStackListener); } /** * Stores the listener received from {@link com.android.server.display.DisplayModeDirector}. * * DisplayModeDirector implements {@link IUdfpsHbmListener} and registers it with this class by * calling {@link CommandQueue#setUdfpsHbmListener(IUdfpsHbmListener)}. */ @Override public void setUdfpsHbmListener(IUdfpsHbmListener listener) { mUdfpsHbmListener = listener; } /** * @return IUdfpsHbmListener that can be set by DisplayModeDirector. */ @Nullable public IUdfpsHbmListener getUdfpsHbmListener() { return mUdfpsHbmListener; } @Override public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver, int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, int userId, long operationId, String opPackageName, long requestId, @BiometricMultiSensorMode int multiSensorConfig) { @Authenticators.Types final int authenticators = promptInfo.getAuthenticators(); if (DEBUG) { StringBuilder ids = new StringBuilder(); for (int sensorId : sensorIds) { ids.append(sensorId).append(" "); } Log.d(TAG, "showAuthenticationDialog, authenticators: " + authenticators + ", sensorIds: " + ids.toString() + ", credentialAllowed: " + credentialAllowed + ", requireConfirmation: " + requireConfirmation + ", operationId: " + operationId + ", requestId: " + requestId + ", multiSensorConfig: " + multiSensorConfig); } SomeArgs args = SomeArgs.obtain(); args.arg1 = promptInfo; args.arg2 = receiver; args.arg3 = sensorIds; args.arg4 = credentialAllowed; args.arg5 = requireConfirmation; args.argi1 = userId; args.arg6 = opPackageName; args.arg7 = operationId; args.arg8 = requestId; args.argi2 = multiSensorConfig; boolean skipAnimation = false; if (mCurrentDialog != null) { Log.w(TAG, "mCurrentDialog: " + mCurrentDialog); skipAnimation = true; } showDialog(args, skipAnimation, null /* savedState */); } /** * Only called via BiometricService for the biometric prompt. Will not be called for * authentication directly requested through FingerprintManager. For * example, KeyguardUpdateMonitor has its own {@link FingerprintManager.AuthenticationCallback}. */ @Override public void onBiometricAuthenticated() { if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: "); if (mCurrentDialog != null) { mCurrentDialog.onAuthenticationSucceeded(); } else { Log.w(TAG, "onBiometricAuthenticated callback but dialog gone"); } } @Override public void onBiometricHelp(@Modality int modality, String message) { if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message); if (mCurrentDialog != null) { mCurrentDialog.onHelp(modality, message); } else { Log.w(TAG, "onBiometricHelp callback but dialog gone"); } } @Nullable public List getUdfpsProps() { return mUdfpsProps; } private String getErrorString(@Modality int modality, int error, int vendorCode) { switch (modality) { case TYPE_FACE: return FaceManager.getErrorString(mContext, error, vendorCode); case TYPE_FINGERPRINT: return FingerprintManager.getErrorString(mContext, error, vendorCode); default: return ""; } } /** * Only called via BiometricService for the biometric prompt. Will not be called for * authentication directly requested through FingerprintManager. For * example, KeyguardUpdateMonitor has its own {@link FingerprintManager.AuthenticationCallback}. */ @Override public void onBiometricError(@Modality int modality, int error, int vendorCode) { if (DEBUG) { Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode)); } final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT) || (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT); boolean isCameraPrivacyEnabled = false; if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE && mSensorPrivacyManager.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, mCurrentDialogArgs.argi1 /* userId */)) { isCameraPrivacyEnabled = true; } // TODO(b/141025588): Create separate methods for handling hard and soft errors. final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT || isCameraPrivacyEnabled); if (mCurrentDialog != null) { if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) { if (DEBUG) Log.d(TAG, "onBiometricError, lockout"); mCurrentDialog.animateToCredentialUI(); } else if (isSoftError) { final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED) ? mContext.getString(R.string.biometric_not_recognized) : getErrorString(modality, error, vendorCode); if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage); // The camera privacy error can return before the prompt initializes its state, // causing the prompt to appear to endlessly authenticate. Add a small delay // to stop this. if (isCameraPrivacyEnabled) { mHandler.postDelayed(() -> { mCurrentDialog.onAuthenticationFailed(modality, mContext.getString(R.string.face_sensor_privacy_enabled)); }, SENSOR_PRIVACY_DELAY); } else { mCurrentDialog.onAuthenticationFailed(modality, errorMessage); } } else { final String errorMessage = getErrorString(modality, error, vendorCode); if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage); mCurrentDialog.onError(modality, errorMessage); } } else { Log.w(TAG, "onBiometricError callback but dialog is gone"); } onCancelUdfps(); } @Override public void hideAuthenticationDialog() { if (DEBUG) Log.d(TAG, "hideAuthenticationDialog: " + mCurrentDialog); if (mCurrentDialog == null) { // Could be possible if the caller canceled authentication after credential success // but before the client was notified. if (DEBUG) Log.d(TAG, "dialog already gone"); return; } mCurrentDialog.dismissFromSystemServer(); // BiometricService will have already sent the callback to the client in this case. // This avoids a round trip to SystemUI. So, just dismiss the dialog and we're done. mCurrentDialog = null; mOrientationListener.disable(); } /** * Whether the user's finger is currently on udfps attempting to authenticate. */ public boolean isUdfpsFingerDown() { if (mUdfpsController == null) { return false; } return mUdfpsController.isFingerDown(); } /** * Whether the passed userId has enrolled face auth. */ public boolean isFaceAuthEnrolled(int userId) { if (mFaceProps == null) { return false; } return mFaceManager.hasEnrolledTemplates(userId); } /** * Whether the passed userId has enrolled UDFPS. */ public boolean isUdfpsEnrolled(int userId) { if (mUdfpsController == null) { return false; } return mUdfpsEnrolledForUser.get(userId); } private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) { mCurrentDialogArgs = args; final PromptInfo promptInfo = (PromptInfo) args.arg1; final int[] sensorIds = (int[]) args.arg3; final boolean credentialAllowed = (boolean) args.arg4; final boolean requireConfirmation = (boolean) args.arg5; final int userId = args.argi1; final String opPackageName = (String) args.arg6; final long operationId = (long) args.arg7; final long requestId = (long) args.arg8; final @BiometricMultiSensorMode int multiSensorConfig = args.argi2; // Create a new dialog but do not replace the current one yet. final AuthDialog newDialog = buildDialog( promptInfo, requireConfirmation, userId, sensorIds, credentialAllowed, opPackageName, skipAnimation, operationId, requestId, multiSensorConfig); if (newDialog == null) { Log.e(TAG, "Unsupported type configuration"); return; } if (DEBUG) { Log.d(TAG, "userId: " + userId + " savedState: " + savedState + " mCurrentDialog: " + mCurrentDialog + " newDialog: " + newDialog); } if (mCurrentDialog != null) { // If somehow we're asked to show a dialog, the old one doesn't need to be animated // away. This can happen if the app cancels and re-starts auth during configuration // change. This is ugly because we also have to do things on onConfigurationChanged // here. mCurrentDialog.dismissWithoutCallback(false /* animate */); } mReceiver = (IBiometricSysuiReceiver) args.arg2; mCurrentDialog = newDialog; mCurrentDialog.show(mWindowManager, savedState); mOrientationListener.enable(); } private void onDialogDismissed(@DismissedReason int reason) { if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason); if (mCurrentDialog == null) { Log.w(TAG, "Dialog already dismissed"); } mReceiver = null; mCurrentDialog = null; mOrientationListener.disable(); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); updateFingerprintLocation(); // Save the state of the current dialog (buttons showing, etc) if (mCurrentDialog != null) { final Bundle savedState = new Bundle(); mCurrentDialog.onSaveState(savedState); mCurrentDialog.dismissWithoutCallback(false /* animate */); mCurrentDialog = null; mOrientationListener.disable(); // Only show the dialog if necessary. If it was animating out, the dialog is supposed // to send its pending callback immediately. if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE) != AuthContainerView.STATE_ANIMATING_OUT) { final boolean credentialShowing = savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING); if (credentialShowing) { // There may be a cleaner way to do this, rather than altering the current // authentication's parameters. This gets the job done and should be clear // enough for now. PromptInfo promptInfo = (PromptInfo) mCurrentDialogArgs.arg1; promptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL); } showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState); } } } private void onOrientationChanged() { updateFingerprintLocation(); if (mCurrentDialog != null) { mCurrentDialog.onOrientationChanged(); } } protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName, boolean skipIntro, long operationId, long requestId, @BiometricMultiSensorMode int multiSensorConfig) { return new AuthContainerView.Builder(mContext) .setCallback(this) .setPromptInfo(promptInfo) .setRequireConfirmation(requireConfirmation) .setUserId(userId) .setOpPackageName(opPackageName) .setSkipIntro(skipIntro) .setOperationId(operationId) .setRequestId(requestId) .setMultiSensorConfig(multiSensorConfig) .build(sensorIds, credentialAllowed, mFpProps, mFaceProps); } /** * AuthController callback used to receive signal for when biometric authenticators are * registered. */ public interface Callback { /** * Called when authenticators are registered. If authenticators are already * registered before this call, this callback will never be triggered. */ void onAllAuthenticatorsRegistered(); /** * Called when UDFPS enrollments have changed. This is called after boot and on changes to * enrollment. */ void onEnrollmentsChanged(); } }