1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.biometrics; 18 19 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; 20 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; 21 22 import android.content.Context; 23 import android.hardware.biometrics.BiometricAuthenticator.Modality; 24 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; 25 import android.os.Bundle; 26 import android.util.AttributeSet; 27 import android.util.Log; 28 import android.view.View; 29 import android.widget.ImageView; 30 import android.widget.TextView; 31 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.systemui.R; 37 38 /** 39 * Manages the layout of an auth dialog for devices with both a face sensor and a fingerprint 40 * sensor. Face authentication is attempted first, followed by fingerprint if the initial attempt is 41 * unsuccessful. 42 */ 43 public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView { 44 private static final String TAG = "BiometricPrompt/AuthBiometricFaceToFingerprintView"; 45 46 protected static class UdfpsIconController extends IconController { 47 @BiometricState private int mIconState = STATE_IDLE; 48 UdfpsIconController( @onNull Context context, @NonNull ImageView iconView, @NonNull TextView textView)49 protected UdfpsIconController( 50 @NonNull Context context, @NonNull ImageView iconView, @NonNull TextView textView) { 51 super(context, iconView, textView); 52 } 53 updateState(@iometricState int newState)54 void updateState(@BiometricState int newState) { 55 updateState(mIconState, newState); 56 } 57 58 @Override updateState(int lastState, int newState)59 protected void updateState(int lastState, int newState) { 60 final boolean lastStateIsErrorIcon = 61 lastState == STATE_ERROR || lastState == STATE_HELP; 62 63 switch (newState) { 64 case STATE_IDLE: 65 case STATE_AUTHENTICATING_ANIMATING_IN: 66 case STATE_AUTHENTICATING: 67 case STATE_PENDING_CONFIRMATION: 68 case STATE_AUTHENTICATED: 69 if (lastStateIsErrorIcon) { 70 animateOnce(R.drawable.fingerprint_dialog_error_to_fp); 71 } else { 72 showStaticDrawable(R.drawable.fingerprint_dialog_fp_to_error); 73 } 74 mIconView.setContentDescription(mContext.getString( 75 R.string.accessibility_fingerprint_dialog_fingerprint_icon)); 76 break; 77 78 case STATE_ERROR: 79 case STATE_HELP: 80 if (!lastStateIsErrorIcon) { 81 animateOnce(R.drawable.fingerprint_dialog_fp_to_error); 82 } else { 83 showStaticDrawable(R.drawable.fingerprint_dialog_error_to_fp); 84 } 85 mIconView.setContentDescription(mContext.getString( 86 R.string.biometric_dialog_try_again)); 87 break; 88 89 default: 90 Log.e(TAG, "Unknown biometric dialog state: " + newState); 91 break; 92 } 93 94 mState = newState; 95 mIconState = newState; 96 } 97 } 98 99 @Modality private int mActiveSensorType = TYPE_FACE; 100 @Nullable private ModalityListener mModalityListener; 101 @Nullable private FingerprintSensorPropertiesInternal mFingerprintSensorProps; 102 @Nullable private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter; 103 @Nullable @VisibleForTesting UdfpsIconController mUdfpsIconController; 104 105 AuthBiometricFaceToFingerprintView(Context context)106 public AuthBiometricFaceToFingerprintView(Context context) { 107 super(context); 108 } 109 AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs)110 public AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs) { 111 super(context, attrs); 112 } 113 114 @VisibleForTesting AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs, Injector injector)115 AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs, Injector injector) { 116 super(context, attrs, injector); 117 } 118 119 @Override onFinishInflate()120 protected void onFinishInflate() { 121 super.onFinishInflate(); 122 mUdfpsIconController = new UdfpsIconController(mContext, mIconView, mIndicatorView); 123 } 124 125 @Modality getActiveSensorType()126 int getActiveSensorType() { 127 return mActiveSensorType; 128 } 129 isFingerprintUdfps()130 boolean isFingerprintUdfps() { 131 return mFingerprintSensorProps.isAnyUdfpsType(); 132 } 133 setModalityListener(@onNull ModalityListener listener)134 void setModalityListener(@NonNull ModalityListener listener) { 135 mModalityListener = listener; 136 } 137 setFingerprintSensorProps(@onNull FingerprintSensorPropertiesInternal sensorProps)138 void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) { 139 mFingerprintSensorProps = sensorProps; 140 } 141 142 @Override getDelayAfterAuthenticatedDurationMs()143 protected int getDelayAfterAuthenticatedDurationMs() { 144 return mActiveSensorType == TYPE_FINGERPRINT ? 0 145 : super.getDelayAfterAuthenticatedDurationMs(); 146 } 147 148 @Override supportsManualRetry()149 protected boolean supportsManualRetry() { 150 return false; 151 } 152 153 @Override onAuthenticationFailed( @odality int modality, @Nullable String failureReason)154 public void onAuthenticationFailed( 155 @Modality int modality, @Nullable String failureReason) { 156 super.onAuthenticationFailed(modality, checkErrorForFallback(failureReason)); 157 } 158 159 @Override onError(int modality, String error)160 public void onError(int modality, String error) { 161 super.onError(modality, checkErrorForFallback(error)); 162 } 163 checkErrorForFallback(String message)164 private String checkErrorForFallback(String message) { 165 if (mActiveSensorType == TYPE_FACE) { 166 Log.d(TAG, "Falling back to fingerprint: " + message); 167 168 // switching from face -> fingerprint mode, suppress root error messages 169 mCallback.onAction(Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR); 170 return mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead); 171 } 172 return message; 173 } 174 175 @Override 176 @BiometricState getStateForAfterError()177 protected int getStateForAfterError() { 178 if (mActiveSensorType == TYPE_FACE) { 179 return STATE_AUTHENTICATING; 180 } 181 182 return super.getStateForAfterError(); 183 } 184 185 @Override updateState(@iometricState int newState)186 public void updateState(@BiometricState int newState) { 187 if (mActiveSensorType == TYPE_FACE) { 188 if (newState == STATE_HELP || newState == STATE_ERROR) { 189 mActiveSensorType = TYPE_FINGERPRINT; 190 191 setRequireConfirmation(false); 192 mConfirmButton.setEnabled(false); 193 mConfirmButton.setVisibility(View.GONE); 194 195 if (mModalityListener != null) { 196 mModalityListener.onModalitySwitched(TYPE_FACE, mActiveSensorType); 197 } 198 199 // Deactivate the face icon controller so it stops drawing to the view 200 mFaceIconController.deactivate(); 201 // Then, activate this icon controller. We need to start in the "idle" state 202 mUdfpsIconController.updateState(STATE_IDLE); 203 } 204 } else { // Fingerprint 205 mUdfpsIconController.updateState(newState); 206 } 207 208 super.updateState(newState); 209 } 210 211 @Override 212 @NonNull onMeasureInternal(int width, int height)213 AuthDialog.LayoutParams onMeasureInternal(int width, int height) { 214 final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height); 215 return isFingerprintUdfps() 216 ? getUdfpsMeasureAdapter().onMeasureInternal(width, height, layoutParams) 217 : layoutParams; 218 } 219 220 @NonNull getUdfpsMeasureAdapter()221 private UdfpsDialogMeasureAdapter getUdfpsMeasureAdapter() { 222 if (mUdfpsMeasureAdapter == null 223 || mUdfpsMeasureAdapter.getSensorProps() != mFingerprintSensorProps) { 224 mUdfpsMeasureAdapter = new UdfpsDialogMeasureAdapter(this, mFingerprintSensorProps); 225 } 226 return mUdfpsMeasureAdapter; 227 } 228 229 @Override onSaveState(@onNull Bundle outState)230 public void onSaveState(@NonNull Bundle outState) { 231 super.onSaveState(outState); 232 outState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, mActiveSensorType); 233 outState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS, mFingerprintSensorProps); 234 } 235 236 @Override restoreState(@ullable Bundle savedState)237 public void restoreState(@Nullable Bundle savedState) { 238 super.restoreState(savedState); 239 if (savedState != null) { 240 mActiveSensorType = savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FACE); 241 mFingerprintSensorProps = 242 savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS); 243 } 244 } 245 } 246