| /* |
| * 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 static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR; |
| |
| 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.Point; |
| import android.graphics.Rect; |
| 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.BiometricStateListener; |
| import android.hardware.biometrics.IBiometricContextListener; |
| 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.face.IFaceAuthenticatorsRegisteredCallback; |
| import android.hardware.fingerprint.FingerprintManager; |
| import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; |
| import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; |
| import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.RemoteException; |
| import android.os.UserManager; |
| import android.util.Log; |
| import android.util.RotationUtils; |
| import android.util.SparseBooleanArray; |
| import android.view.Display; |
| import android.view.DisplayInfo; |
| import android.view.MotionEvent; |
| import android.view.WindowManager; |
| |
| import com.android.internal.R; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.jank.InteractionJankMonitor; |
| import com.android.internal.os.SomeArgs; |
| import com.android.internal.widget.LockPatternUtils; |
| import com.android.settingslib.udfps.UdfpsOverlayParams; |
| import com.android.settingslib.udfps.UdfpsUtils; |
| import com.android.systemui.CoreStartable; |
| import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor; |
| import com.android.systemui.biometrics.domain.interactor.LogContextInteractor; |
| import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel; |
| import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel; |
| import com.android.systemui.dagger.SysUISingleton; |
| import com.android.systemui.dagger.qualifiers.Background; |
| import com.android.systemui.dagger.qualifiers.Main; |
| import com.android.systemui.doze.DozeReceiver; |
| import com.android.systemui.keyguard.WakefulnessLifecycle; |
| import com.android.systemui.keyguard.data.repository.BiometricType; |
| import com.android.systemui.statusbar.CommandQueue; |
| import com.android.systemui.statusbar.VibratorHelper; |
| import com.android.systemui.util.concurrency.DelayableExecutor; |
| import com.android.systemui.util.concurrency.Execution; |
| |
| import kotlin.Unit; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| import javax.inject.Inject; |
| import javax.inject.Provider; |
| |
| /** |
| * 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 implements CoreStartable, 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 Context mContext; |
| 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<UdfpsController> mUdfpsControllerFactory; |
| private final Provider<SideFpsController> mSidefpsControllerFactory; |
| |
| // TODO: these should be migrated out once ready |
| @NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor; |
| |
| @NonNull private final Provider<AuthBiometricFingerprintViewModel> |
| mAuthBiometricFingerprintViewModelProvider; |
| @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider; |
| @NonNull private final LogContextInteractor mLogContextInteractor; |
| |
| private final Display mDisplay; |
| private float mScaleFactor = 1f; |
| // sensor locations without any resolution scaling nor rotation adjustments: |
| @Nullable private final Point mFaceSensorLocationDefault; |
| // cached sensor locations: |
| @Nullable private Point mFaceSensorLocation; |
| @Nullable private Point mFingerprintSensorLocation; |
| @Nullable private Rect mUdfpsBounds; |
| private final Set<Callback> mCallbacks = new HashSet<>(); |
| |
| // TODO: These should just be saved from onSaveState |
| private SomeArgs mCurrentDialogArgs; |
| @VisibleForTesting |
| AuthDialog mCurrentDialog; |
| |
| @NonNull private final WindowManager mWindowManager; |
| @NonNull private final DisplayManager mDisplayManager; |
| @Nullable private UdfpsController mUdfpsController; |
| @Nullable private UdfpsOverlayParams mUdfpsOverlayParams; |
| @Nullable private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback; |
| @Nullable private SideFpsController mSideFpsController; |
| @Nullable private UdfpsLogger mUdfpsLogger; |
| @VisibleForTesting IBiometricSysuiReceiver mReceiver; |
| @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener; |
| @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps; |
| @Nullable private List<FingerprintSensorPropertiesInternal> mFpProps; |
| @Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps; |
| @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps; |
| |
| @NonNull private final Map<Integer, Boolean> mFpEnrolledForUser = new HashMap<>(); |
| @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; |
| @NonNull private final SparseBooleanArray mFaceEnrolledForUser; |
| @NonNull private final SparseBooleanArray mSfpsEnrolledForUser; |
| @NonNull private final SensorPrivacyManager mSensorPrivacyManager; |
| private final WakefulnessLifecycle mWakefulnessLifecycle; |
| private final AuthDialogPanelInteractionDetector mPanelInteractionDetector; |
| private boolean mAllFingerprintAuthenticatorsRegistered; |
| @NonNull private final UserManager mUserManager; |
| @NonNull private final LockPatternUtils mLockPatternUtils; |
| @NonNull private final InteractionJankMonitor mInteractionJankMonitor; |
| @NonNull private final UdfpsUtils mUdfpsUtils; |
| private final @Background DelayableExecutor mBackgroundExecutor; |
| private final DisplayInfo mCachedDisplayInfo = new DisplayInfo(); |
| |
| private final VibratorHelper mVibratorHelper; |
| |
| private void vibrateSuccess(int modality) { |
| mVibratorHelper.vibrateAuthSuccess( |
| getClass().getSimpleName() + ", modality = " + modality + "BP::success"); |
| } |
| |
| private void vibrateError(int modality) { |
| mVibratorHelper.vibrateAuthError( |
| getClass().getSimpleName() + ", modality = " + modality + "BP::error"); |
| } |
| |
| @VisibleForTesting |
| final TaskStackListener mTaskStackListener = new TaskStackListener() { |
| @Override |
| public void onTaskStackChanged() { |
| mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground); |
| } |
| }; |
| |
| @VisibleForTesting |
| final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { |
| String reason = intent.getStringExtra("reason"); |
| reason = (reason != null) ? reason : "unknown"; |
| closeDioalog(reason); |
| } |
| } |
| }; |
| |
| private void closeDioalog(String reason) { |
| if (isShowing()) { |
| Log.i(TAG, "Close BP, reason :" + reason); |
| mCurrentDialog.dismissWithoutCallback(true /* animate */); |
| mCurrentDialog = null; |
| |
| for (Callback cb : mCallbacks) { |
| cb.onBiometricPromptDismissed(); |
| } |
| |
| 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 cancelIfOwnerIsNotInForeground() { |
| mExecution.assertIsMainThread(); |
| if (mCurrentDialog != null) { |
| try { |
| final String clientPackage = mCurrentDialog.getOpPackageName(); |
| Log.w(TAG, "Task stack changed, current client: " + clientPackage); |
| final List<ActivityManager.RunningTaskInfo> runningTasks = |
| mActivityTaskManager.getTasks(1); |
| if (!runningTasks.isEmpty()) { |
| final String topPackage = runningTasks.get(0).topActivity.getPackageName(); |
| if (!topPackage.contentEquals(clientPackage) |
| && !Utils.isSystem(mContext, clientPackage)) { |
| Log.e(TAG, "Evicting client due to: " + topPackage); |
| mCurrentDialog.dismissWithoutCallback(true /* animate */); |
| mCurrentDialog = null; |
| |
| for (Callback cb : mCallbacks) { |
| cb.onBiometricPromptDismissed(); |
| } |
| |
| if (mReceiver != null) { |
| mReceiver.onDialogDismissed( |
| BiometricPrompt.DISMISSED_REASON_USER_CANCEL, |
| null /* credentialAttestation */); |
| mReceiver = null; |
| } |
| } |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "Remote exception", e); |
| } |
| } |
| } |
| |
| /** |
| * Whether all fingerprint authentictors have been registered. |
| */ |
| public boolean areAllFingerprintAuthenticatorsRegistered() { |
| return mAllFingerprintAuthenticatorsRegistered; |
| } |
| |
| private void handleAllFingerprintAuthenticatorsRegistered( |
| List<FingerprintSensorPropertiesInternal> sensors) { |
| mExecution.assertIsMainThread(); |
| if (DEBUG) { |
| Log.d(TAG, "handleAllFingerprintAuthenticatorsRegistered | sensors: " |
| + Arrays.toString(sensors.toArray())); |
| } |
| mAllFingerprintAuthenticatorsRegistered = true; |
| mFpProps = sensors; |
| |
| List<FingerprintSensorPropertiesInternal> udfpsProps = new ArrayList<>(); |
| List<FingerprintSensorPropertiesInternal> 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(); |
| mUdfpsController.addCallback(new UdfpsController.Callback() { |
| @Override |
| public void onFingerUp() { |
| } |
| |
| @Override |
| public void onFingerDown() { |
| if (mCurrentDialog != null) { |
| mCurrentDialog.onPointerDown(); |
| } |
| } |
| }); |
| mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation); |
| mUdfpsController.setUdfpsDisplayMode(new UdfpsDisplayMode(mContext, mExecution, |
| this, mUdfpsLogger)); |
| mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect(); |
| } |
| |
| mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null; |
| if (mSidefpsProps != null) { |
| mSideFpsController = mSidefpsControllerFactory.get(); |
| } |
| |
| mFingerprintManager.registerBiometricStateListener(new BiometricStateListener() { |
| @Override |
| public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { |
| mHandler.post(() -> handleEnrollmentsChanged( |
| TYPE_FINGERPRINT, userId, sensorId, hasEnrollments)); |
| } |
| }); |
| updateSensorLocations(); |
| |
| for (Callback cb : mCallbacks) { |
| cb.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT); |
| } |
| } |
| |
| private void handleAllFaceAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) { |
| mExecution.assertIsMainThread(); |
| if (DEBUG) { |
| Log.d(TAG, "handleAllFaceAuthenticatorsRegistered | sensors: " + Arrays.toString( |
| sensors.toArray())); |
| } |
| |
| mFaceManager.registerBiometricStateListener(new BiometricStateListener() { |
| @Override |
| public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { |
| mHandler.post(() -> handleEnrollmentsChanged( |
| TYPE_FACE, userId, sensorId, hasEnrollments)); |
| } |
| }); |
| |
| for (Callback cb : mCallbacks) { |
| cb.onAllAuthenticatorsRegistered(TYPE_FACE); |
| } |
| } |
| |
| private void handleEnrollmentsChanged(@Modality int modality, int userId, int sensorId, |
| boolean hasEnrollments) { |
| mExecution.assertIsMainThread(); |
| Log.d(TAG, "handleEnrollmentsChanged, userId: " + userId + ", sensorId: " + sensorId |
| + ", hasEnrollments: " + hasEnrollments); |
| BiometricType sensorBiometricType = BiometricType.UNKNOWN; |
| if (mFpProps != null) { |
| for (FingerprintSensorPropertiesInternal prop: mFpProps) { |
| if (prop.sensorId == sensorId) { |
| mFpEnrolledForUser.put(userId, hasEnrollments); |
| if (prop.isAnyUdfpsType()) { |
| sensorBiometricType = BiometricType.UNDER_DISPLAY_FINGERPRINT; |
| mUdfpsEnrolledForUser.put(userId, hasEnrollments); |
| } else if (prop.isAnySidefpsType()) { |
| sensorBiometricType = BiometricType.SIDE_FINGERPRINT; |
| mSfpsEnrolledForUser.put(userId, hasEnrollments); |
| } else if (prop.sensorType == TYPE_REAR) { |
| sensorBiometricType = BiometricType.REAR_FINGERPRINT; |
| } |
| break; |
| } |
| } |
| } |
| if (mFaceProps == null) { |
| Log.d(TAG, "handleEnrollmentsChanged, mFaceProps is null"); |
| } else { |
| for (FaceSensorPropertiesInternal prop : mFaceProps) { |
| if (prop.sensorId == sensorId) { |
| mFaceEnrolledForUser.put(userId, hasEnrollments); |
| sensorBiometricType = BiometricType.FACE; |
| break; |
| } |
| } |
| } |
| for (Callback cb : mCallbacks) { |
| cb.onEnrollmentsChanged(modality); |
| cb.onEnrollmentsChanged(sensorBiometricType, userId, hasEnrollments); |
| } |
| } |
| |
| /** |
| * 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(long requestId) { |
| final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId); |
| if (receiver == null) { |
| Log.w(TAG, "Skip onTryAgainPressed"); |
| return; |
| } |
| |
| try { |
| receiver.onTryAgainPressed(); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException when handling try again", e); |
| } |
| } |
| |
| @Override |
| public void onDeviceCredentialPressed(long requestId) { |
| final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId); |
| if (receiver == null) { |
| Log.w(TAG, "Skip onDeviceCredentialPressed"); |
| return; |
| } |
| |
| try { |
| receiver.onDeviceCredentialPressed(); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException when handling credential button", e); |
| } |
| } |
| |
| @Override |
| public void onSystemEvent(int event, long requestId) { |
| final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId); |
| if (receiver == null) { |
| Log.w(TAG, "Skip onSystemEvent"); |
| return; |
| } |
| |
| try { |
| receiver.onSystemEvent(event); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException when sending system event", e); |
| } |
| } |
| |
| @Override |
| public void onDialogAnimatedIn(long requestId) { |
| final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId); |
| if (receiver == null) { |
| Log.w(TAG, "Skip onDialogAnimatedIn"); |
| return; |
| } |
| |
| try { |
| receiver.onDialogAnimatedIn(); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e); |
| } |
| } |
| |
| @Nullable |
| private IBiometricSysuiReceiver getCurrentReceiver(long requestId) { |
| if (!isRequestIdValid(requestId)) { |
| return null; |
| } |
| |
| if (mReceiver == null) { |
| Log.w(TAG, "getCurrentReceiver: Receiver is null"); |
| } |
| |
| return mReceiver; |
| } |
| |
| private boolean isRequestIdValid(long requestId) { |
| if (mCurrentDialog == null) { |
| Log.w(TAG, "shouldNotifyReceiver: dialog already gone"); |
| return false; |
| } |
| |
| if (requestId != mCurrentDialog.getRequestId()) { |
| Log.w(TAG, "shouldNotifyReceiver: requestId doesn't match"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public void onDismissed(@DismissedReason int reason, |
| @Nullable byte[] credentialAttestation, long requestId) { |
| |
| if (mCurrentDialog != null && requestId != mCurrentDialog.getRequestId()) { |
| Log.w(TAG, "requestId doesn't match, skip onDismissed"); |
| return; |
| } |
| |
| 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; |
| } |
| } |
| |
| @Override |
| public void handleShowGlobalActionsMenu() { |
| closeDioalog("PowerMenu shown"); |
| } |
| |
| /** |
| * @return where the UDFPS exists on the screen in pixels in portrait mode. |
| */ |
| @Nullable public Point getUdfpsLocation() { |
| if (mUdfpsController == null || mUdfpsBounds == null) { |
| return null; |
| } |
| return new Point(mUdfpsBounds.centerX(), mUdfpsBounds.centerY()); |
| } |
| |
| /** |
| * @return the radius of UDFPS on the screen in pixels |
| */ |
| public float getUdfpsRadius() { |
| if (mUdfpsController == null || mUdfpsBounds == null) { |
| return -1; |
| } |
| return mUdfpsBounds.height() / 2f; |
| } |
| |
| /** |
| * Gets the cached scale factor representing the user's current resolution / the stable |
| * (default) resolution. |
| */ |
| public float getScaleFactor() { |
| return mScaleFactor; |
| } |
| |
| /** |
| * Updates the current display info and cached scale factor & sensor locations. |
| * Getting the display info is a relatively expensive call, so avoid superfluous calls. |
| */ |
| private void updateSensorLocations() { |
| mDisplay.getDisplayInfo(mCachedDisplayInfo); |
| mScaleFactor = mUdfpsUtils.getScaleFactor(mCachedDisplayInfo); |
| updateUdfpsLocation(); |
| updateFingerprintLocation(); |
| updateFaceLocation(); |
| } |
| /** |
| * @return where the fingerprint sensor exists in pixels in its natural orientation. |
| * Devices without location configs will use the default value even if they don't have a |
| * fingerprint sensor. |
| * |
| * May return null if the fingerprint sensor isn't available yet. |
| */ |
| @Nullable private Point getFingerprintSensorLocationInNaturalOrientation() { |
| if (getUdfpsLocation() != null) { |
| return getUdfpsLocation(); |
| } else { |
| int xFpLocation = mCachedDisplayInfo.getNaturalWidth() / 2; |
| try { |
| xFpLocation = mContext.getResources().getDimensionPixelSize( |
| com.android.systemui.R.dimen |
| .physical_fingerprint_sensor_center_screen_location_x); |
| } catch (Resources.NotFoundException e) { |
| } |
| |
| return new Point( |
| (int) (xFpLocation * mScaleFactor), |
| (int) (mContext.getResources().getDimensionPixelSize( |
| com.android.systemui.R.dimen |
| .physical_fingerprint_sensor_center_screen_location_y) |
| * mScaleFactor) |
| ); |
| } |
| } |
| |
| /** |
| * @return where the fingerprint sensor exists in pixels exists the current device orientation. |
| * Devices without location configs will use the default value even if they don't have a |
| * fingerprint sensor. |
| */ |
| @Nullable public Point getFingerprintSensorLocation() { |
| return mFingerprintSensorLocation; |
| } |
| |
| private void updateFingerprintLocation() { |
| if (mFpProps == null) { |
| mFingerprintSensorLocation = null; |
| } else { |
| mFingerprintSensorLocation = rotateToCurrentOrientation( |
| getFingerprintSensorLocationInNaturalOrientation(), |
| mCachedDisplayInfo); |
| } |
| |
| for (final Callback cb : mCallbacks) { |
| cb.onFingerprintLocationChanged(); |
| } |
| } |
| |
| /** Get FP sensor properties */ |
| public @Nullable List<FingerprintSensorPropertiesInternal> getFingerprintProperties() { |
| return mFpProps; |
| } |
| |
| /** |
| * @return where the face sensor exists in pixels in the current device orientation. Returns |
| * null if no face sensor exists. |
| */ |
| @Nullable public Point getFaceSensorLocation() { |
| return mFaceSensorLocation; |
| } |
| |
| private void updateFaceLocation() { |
| if (mFaceProps == null || mFaceSensorLocationDefault == null) { |
| mFaceSensorLocation = null; |
| } else { |
| mFaceSensorLocation = rotateToCurrentOrientation( |
| new Point( |
| (int) (mFaceSensorLocationDefault.x * mScaleFactor), |
| (int) (mFaceSensorLocationDefault.y * mScaleFactor)), |
| mCachedDisplayInfo |
| ); |
| } |
| |
| for (final Callback cb : mCallbacks) { |
| cb.onFaceSensorLocationChanged(); |
| } |
| } |
| |
| /** |
| * @param inOutPoint point on the display in pixels. Going in, represents the point |
| * in the device's natural orientation. Going out, represents |
| * the point in the display's current orientation. |
| * @param displayInfo currently display information to use to rotate the point |
| */ |
| @VisibleForTesting |
| protected Point rotateToCurrentOrientation(Point inOutPoint, DisplayInfo displayInfo) { |
| RotationUtils.rotatePoint( |
| inOutPoint, |
| displayInfo.rotation, |
| displayInfo.getNaturalWidth(), |
| displayInfo.getNaturalHeight() |
| ); |
| return inOutPoint; |
| } |
| |
| /** |
| * 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); |
| } |
| |
| 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<UdfpsController> udfpsControllerFactory, |
| Provider<SideFpsController> sidefpsControllerFactory, |
| @NonNull DisplayManager displayManager, |
| @NonNull WakefulnessLifecycle wakefulnessLifecycle, |
| @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, |
| @NonNull UserManager userManager, |
| @NonNull LockPatternUtils lockPatternUtils, |
| @NonNull UdfpsLogger udfpsLogger, |
| @NonNull LogContextInteractor logContextInteractor, |
| @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor, |
| @NonNull Provider<AuthBiometricFingerprintViewModel> |
| authBiometricFingerprintViewModelProvider, |
| @NonNull Provider<CredentialViewModel> credentialViewModelProvider, |
| @NonNull InteractionJankMonitor jankMonitor, |
| @Main Handler handler, |
| @Background DelayableExecutor bgExecutor, |
| @NonNull VibratorHelper vibrator, |
| @NonNull UdfpsUtils udfpsUtils) { |
| mContext = context; |
| mExecution = execution; |
| mUserManager = userManager; |
| mLockPatternUtils = lockPatternUtils; |
| mHandler = handler; |
| mBackgroundExecutor = bgExecutor; |
| mCommandQueue = commandQueue; |
| mActivityTaskManager = activityTaskManager; |
| mFingerprintManager = fingerprintManager; |
| mFaceManager = faceManager; |
| mUdfpsControllerFactory = udfpsControllerFactory; |
| mSidefpsControllerFactory = sidefpsControllerFactory; |
| mUdfpsLogger = udfpsLogger; |
| mDisplayManager = displayManager; |
| mWindowManager = windowManager; |
| mInteractionJankMonitor = jankMonitor; |
| mUdfpsEnrolledForUser = new SparseBooleanArray(); |
| mSfpsEnrolledForUser = new SparseBooleanArray(); |
| mFaceEnrolledForUser = new SparseBooleanArray(); |
| mVibratorHelper = vibrator; |
| mUdfpsUtils = udfpsUtils; |
| |
| mLogContextInteractor = logContextInteractor; |
| mBiometricPromptInteractor = biometricPromptInteractor; |
| mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider; |
| mCredentialViewModelProvider = credentialViewModelProvider; |
| |
| mOrientationListener = new BiometricDisplayListener( |
| context, |
| mDisplayManager, |
| mHandler, |
| BiometricDisplayListener.SensorType.Generic.INSTANCE, |
| () -> { |
| onOrientationChanged(); |
| return Unit.INSTANCE; |
| }); |
| |
| mWakefulnessLifecycle = wakefulnessLifecycle; |
| mPanelInteractionDetector = panelInteractionDetector; |
| |
| |
| 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) { |
| mFaceSensorLocationDefault = null; |
| } else { |
| mFaceSensorLocationDefault = new Point( |
| faceAuthLocation[0], |
| faceAuthLocation[1]); |
| } |
| |
| mDisplay = mContext.getDisplay(); |
| updateSensorLocations(); |
| |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); |
| context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED); |
| mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); |
| } |
| |
| // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this. |
| // This is not combined with updateFingerprintLocation because this is invoked directly from |
| // UdfpsController, only when it cares about a rotation change. The implications of calling |
| // updateFingerprintLocation in such a case are unclear. |
| private void updateUdfpsLocation() { |
| if (mUdfpsController != null) { |
| final FingerprintSensorPropertiesInternal udfpsProp = mUdfpsProps.get(0); |
| |
| final Rect previousUdfpsBounds = mUdfpsBounds; |
| final UdfpsOverlayParams previousUdfpsOverlayParams = mUdfpsOverlayParams; |
| |
| mUdfpsBounds = udfpsProp.getLocation().getRect(); |
| mUdfpsBounds.scale(mScaleFactor); |
| |
| final Rect overlayBounds = new Rect( |
| 0, /* left */ |
| mCachedDisplayInfo.getNaturalHeight() / 2, /* top */ |
| mCachedDisplayInfo.getNaturalWidth(), /* right */ |
| mCachedDisplayInfo.getNaturalHeight() /* bottom */); |
| |
| mUdfpsOverlayParams = new UdfpsOverlayParams( |
| mUdfpsBounds, |
| overlayBounds, |
| mCachedDisplayInfo.getNaturalWidth(), |
| mCachedDisplayInfo.getNaturalHeight(), |
| mScaleFactor, |
| mCachedDisplayInfo.rotation); |
| |
| mUdfpsController.updateOverlayParams(udfpsProp, mUdfpsOverlayParams); |
| if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds) || !Objects.equals( |
| previousUdfpsOverlayParams, mUdfpsOverlayParams)) { |
| for (Callback cb : mCallbacks) { |
| cb.onUdfpsLocationChanged(mUdfpsOverlayParams); |
| } |
| } |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Override |
| public void start() { |
| mCommandQueue.addCallback(this); |
| |
| if (mFingerprintManager != null) { |
| mFingerprintManager.addAuthenticatorsRegisteredCallback( |
| new IFingerprintAuthenticatorsRegisteredCallback.Stub() { |
| @Override |
| public void onAllAuthenticatorsRegistered( |
| List<FingerprintSensorPropertiesInternal> sensors) { |
| mHandler.post(() -> |
| handleAllFingerprintAuthenticatorsRegistered(sensors)); |
| } |
| }); |
| } |
| if (mFaceManager != null) { |
| mFaceManager.addAuthenticatorsRegisteredCallback( |
| new IFaceAuthenticatorsRegisteredCallback.Stub() { |
| @Override |
| public void onAllAuthenticatorsRegistered( |
| List<FaceSensorPropertiesInternal> sensors) { |
| mHandler.post(() -> |
| handleAllFaceAuthenticatorsRegistered(sensors)); |
| } |
| } |
| ); |
| } |
| |
| mActivityTaskManager.registerTaskStackListener(mTaskStackListener); |
| mOrientationListener.enable(); |
| updateSensorLocations(); |
| } |
| |
| @Override |
| public void setBiometricContextListener(IBiometricContextListener listener) { |
| mLogContextInteractor.addBiometricContextListener(listener); |
| } |
| |
| /** |
| * Stores the callback received from |
| * {@link com.android.server.display.mode.DisplayModeDirector}. |
| * |
| * DisplayModeDirector implements {@link IUdfpsRefreshRateRequestCallback} |
| * and registers it with this class by calling |
| * {@link CommandQueue#setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback)}. |
| */ |
| @Override |
| public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) { |
| mUdfpsRefreshRateRequestCallback = callback; |
| } |
| |
| /** |
| * @return IUdfpsRefreshRateRequestCallback that can be set by DisplayModeDirector. |
| */ |
| @Nullable public IUdfpsRefreshRateRequestCallback getUdfpsRefreshRateCallback() { |
| return mUdfpsRefreshRateRequestCallback; |
| } |
| |
| @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.argl1 = operationId; |
| args.argl2 = 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(@Modality int modality) { |
| if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: "); |
| |
| vibrateSuccess(modality); |
| |
| if (mCurrentDialog != null) { |
| mCurrentDialog.onAuthenticationSucceeded(modality); |
| } 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<FingerprintSensorPropertiesInternal> getUdfpsProps() { |
| return mUdfpsProps; |
| } |
| |
| @Nullable |
| public List<FingerprintSensorPropertiesInternal> getSfpsProps() { |
| return mSidefpsProps; |
| } |
| |
| /** |
| * @return true if udfps HW is supported on this device. Can return true even if the user has |
| * not enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered. |
| */ |
| public boolean isUdfpsSupported() { |
| return getUdfpsProps() != null && !getUdfpsProps().isEmpty(); |
| } |
| |
| /** |
| * @return true if sfps HW is supported on this device. Can return true even if the user has |
| * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered. |
| */ |
| public boolean isSfpsSupported() { |
| return getSfpsProps() != null && !getSfpsProps().isEmpty(); |
| } |
| |
| /** |
| * @return true if rear fps HW is supported on this device. Can return true even if the user has |
| * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered. |
| */ |
| public boolean isRearFpsSupported() { |
| if (mFpProps != null) { |
| for (FingerprintSensorPropertiesInternal prop: mFpProps) { |
| if (prop.sensorType == TYPE_REAR) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| |
| 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)); |
| } |
| |
| vibrateError(modality); |
| |
| 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.TOGGLE_TYPE_SOFTWARE, SensorPrivacyManager.Sensors.CAMERA)) { |
| 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"); |
| } |
| } |
| |
| @Override |
| public void hideAuthenticationDialog(long requestId) { |
| 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; |
| } |
| if (requestId != mCurrentDialog.getRequestId()) { |
| Log.w(TAG, "ignore - ids do not match: " + requestId + " current: " |
| + mCurrentDialog.getRequestId()); |
| 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; |
| } |
| |
| /** |
| * 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 mFaceEnrolledForUser.get(userId); |
| } |
| |
| /** |
| * Whether the passed userId has enrolled UDFPS. |
| */ |
| public boolean isUdfpsEnrolled(int userId) { |
| if (mUdfpsController == null) { |
| return false; |
| } |
| |
| return mUdfpsEnrolledForUser.get(userId); |
| } |
| |
| /** |
| * Whether the passed userId has enrolled SFPS. |
| */ |
| public boolean isSfpsEnrolled(int userId) { |
| if (mSideFpsController == null) { |
| return false; |
| } |
| |
| return mSfpsEnrolledForUser.get(userId); |
| } |
| |
| /** If BiometricPrompt is currently being shown to the user. */ |
| public boolean isShowing() { |
| return mCurrentDialog != null; |
| } |
| |
| /** |
| * Whether the passed userId has enrolled at least one fingerprint. |
| */ |
| public boolean isFingerprintEnrolled(int userId) { |
| return mFpEnrolledForUser.getOrDefault(userId, false); |
| } |
| |
| 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 = args.argl1; |
| final long requestId = args.argl2; |
| @BiometricMultiSensorMode final int multiSensorConfig = args.argi2; |
| |
| // Create a new dialog but do not replace the current one yet. |
| final AuthDialog newDialog = buildDialog( |
| mBackgroundExecutor, |
| promptInfo, |
| requireConfirmation, |
| userId, |
| sensorIds, |
| opPackageName, |
| skipAnimation, |
| operationId, |
| requestId, |
| multiSensorConfig, |
| mWakefulnessLifecycle, |
| mPanelInteractionDetector, |
| mUserManager, |
| mLockPatternUtils); |
| |
| 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; |
| for (Callback cb : mCallbacks) { |
| cb.onBiometricPromptShown(); |
| } |
| mCurrentDialog = newDialog; |
| mCurrentDialog.show(mWindowManager, savedState); |
| |
| if (!promptInfo.isAllowBackgroundAuthentication()) { |
| mHandler.post(this::cancelIfOwnerIsNotInForeground); |
| } |
| } |
| |
| private void onDialogDismissed(@DismissedReason int reason) { |
| if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason); |
| if (mCurrentDialog == null) { |
| Log.w(TAG, "Dialog already dismissed"); |
| } |
| |
| for (Callback cb : mCallbacks) { |
| cb.onBiometricPromptDismissed(); |
| } |
| |
| mReceiver = null; |
| mCurrentDialog = null; |
| } |
| |
| @Override |
| public void onConfigurationChanged(Configuration newConfig) { |
| updateSensorLocations(); |
| |
| // 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; |
| |
| // Only show the dialog if necessary. If it was animating out, the dialog is supposed |
| // to send its pending callback immediately. |
| if (!savedState.getBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false)) { |
| 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() { |
| updateSensorLocations(); |
| if (mCurrentDialog != null) { |
| mCurrentDialog.onOrientationChanged(); |
| } |
| } |
| |
| protected AuthDialog buildDialog(@Background DelayableExecutor bgExecutor, |
| PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds, |
| String opPackageName, boolean skipIntro, long operationId, long requestId, |
| @BiometricMultiSensorMode int multiSensorConfig, |
| @NonNull WakefulnessLifecycle wakefulnessLifecycle, |
| @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, |
| @NonNull UserManager userManager, |
| @NonNull LockPatternUtils lockPatternUtils) { |
| return new AuthContainerView.Builder(mContext) |
| .setCallback(this) |
| .setPromptInfo(promptInfo) |
| .setRequireConfirmation(requireConfirmation) |
| .setUserId(userId) |
| .setOpPackageName(opPackageName) |
| .setSkipIntro(skipIntro) |
| .setOperationId(operationId) |
| .setRequestId(requestId) |
| .setMultiSensorConfig(multiSensorConfig) |
| .setScaleFactorProvider(() -> getScaleFactor()) |
| .build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle, |
| panelInteractionDetector, userManager, lockPatternUtils, |
| mInteractionJankMonitor, mBiometricPromptInteractor, |
| mAuthBiometricFingerprintViewModelProvider, mCredentialViewModelProvider); |
| } |
| |
| @Override |
| public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { |
| final AuthDialog dialog = mCurrentDialog; |
| pw.println(" mCachedDisplayInfo=" + mCachedDisplayInfo); |
| pw.println(" mScaleFactor=" + mScaleFactor); |
| pw.println(" faceAuthSensorLocationDefault=" + mFaceSensorLocationDefault); |
| pw.println(" faceAuthSensorLocation=" + getFaceSensorLocation()); |
| pw.println(" fingerprintSensorLocationInNaturalOrientation=" |
| + getFingerprintSensorLocationInNaturalOrientation()); |
| pw.println(" fingerprintSensorLocation=" + getFingerprintSensorLocation()); |
| pw.println(" udfpsBounds=" + mUdfpsBounds); |
| pw.println(" allFingerprintAuthenticatorsRegistered=" |
| + mAllFingerprintAuthenticatorsRegistered); |
| pw.println(" currentDialog=" + dialog); |
| if (dialog != null) { |
| dialog.dump(pw, args); |
| } |
| } |
| |
| /** |
| * Provides a float that represents the resolution scale(if the controller is for UDFPS). |
| */ |
| public interface ScaleFactorProvider { |
| /** |
| * Returns a float representing the scaled resolution(if the controller if for UDFPS). |
| */ |
| float provide(); |
| } |
| |
| /** |
| * 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. |
| */ |
| default void onAllAuthenticatorsRegistered(@Modality int modality) {} |
| |
| /** |
| * Called when enrollments have changed. This is called after boot and on changes to |
| * enrollment. |
| */ |
| default void onEnrollmentsChanged(@Modality int modality) {} |
| |
| /** |
| * Called when enrollments have changed. This is called after boot and on changes to |
| * enrollment. |
| */ |
| default void onEnrollmentsChanged( |
| @NonNull BiometricType biometricType, |
| int userId, |
| boolean hasEnrollments |
| ) {} |
| |
| /** |
| * Called when the biometric prompt starts showing. |
| */ |
| default void onBiometricPromptShown() {} |
| |
| /** |
| * Called when the biometric prompt is no longer showing. |
| */ |
| default void onBiometricPromptDismissed() {} |
| |
| /** |
| * Called when the location of the fingerprint sensor changes. The location in pixels can |
| * change due to resolution changes. |
| */ |
| default void onFingerprintLocationChanged() {} |
| |
| /** |
| * Called when the location of the under display fingerprint sensor changes. The location in |
| * pixels can change due to resolution changes. |
| * |
| * On devices with UDFPS, this is always called alongside |
| * {@link #onFingerprintLocationChanged}. |
| */ |
| default void onUdfpsLocationChanged(UdfpsOverlayParams udfpsOverlayParams) {} |
| |
| /** |
| * Called when the location of the face unlock sensor (typically the front facing camera) |
| * changes. The location in pixels can change due to resolution changes. |
| */ |
| default void onFaceSensorLocationChanged() {} |
| } |
| } |