• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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