• 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 static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertTrue;
24 import static org.mockito.ArgumentMatchers.anyInt;
25 import static org.mockito.ArgumentMatchers.eq;
26 import static org.mockito.Mockito.never;
27 import static org.mockito.Mockito.reset;
28 import static org.mockito.Mockito.verify;
29 
30 import android.content.Context;
31 import android.hardware.biometrics.ComponentInfoInternal;
32 import android.hardware.biometrics.SensorProperties;
33 import android.hardware.fingerprint.FingerprintSensorProperties;
34 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
35 import android.os.Bundle;
36 import android.test.suitebuilder.annotation.SmallTest;
37 import android.testing.AndroidTestingRunner;
38 import android.testing.TestableLooper;
39 import android.view.View;
40 import android.widget.Button;
41 import android.widget.ImageView;
42 import android.widget.TextView;
43 
44 import androidx.annotation.NonNull;
45 
46 import com.android.systemui.R;
47 import com.android.systemui.SysuiTestCase;
48 
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.mockito.Mock;
53 import org.mockito.MockitoAnnotations;
54 
55 import java.util.ArrayList;
56 import java.util.List;
57 
58 @RunWith(AndroidTestingRunner.class)
59 @TestableLooper.RunWithLooper
60 @SmallTest
61 public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
62 
63     @Mock AuthBiometricView.Callback mCallback;
64 
65     private AuthBiometricFaceToFingerprintView mFaceToFpView;
66 
67     @Mock private Button mNegativeButton;
68     @Mock private Button mCancelButton;
69     @Mock private Button mConfirmButton;
70     @Mock private Button mUseCredentialButton;
71     @Mock private Button mTryAgainButton;
72 
73     @Mock private TextView mTitleView;
74     @Mock private TextView mSubtitleView;
75     @Mock private TextView mDescriptionView;
76     @Mock private TextView mIndicatorView;
77     @Mock private ImageView mIconView;
78     @Mock private View mIconHolderView;
79     @Mock private AuthBiometricFaceView.IconController mFaceIconController;
80     @Mock private AuthBiometricFaceToFingerprintView.UdfpsIconController mUdfpsIconController;
81 
82     @Before
setup()83     public void setup() {
84         MockitoAnnotations.initMocks(this);
85 
86         mFaceToFpView = new TestableView(mContext);
87         mFaceToFpView.mFaceIconController = mFaceIconController;
88         mFaceToFpView.mUdfpsIconController = mUdfpsIconController;
89         mFaceToFpView.setCallback(mCallback);
90 
91         mFaceToFpView.mNegativeButton = mNegativeButton;
92         mFaceToFpView.mCancelButton = mCancelButton;
93         mFaceToFpView.mUseCredentialButton = mUseCredentialButton;
94         mFaceToFpView.mConfirmButton = mConfirmButton;
95         mFaceToFpView.mTryAgainButton = mTryAgainButton;
96         mFaceToFpView.mIndicatorView = mIndicatorView;
97     }
98 
99     @Test
testStateUpdated_whenDialogAnimatedIn()100     public void testStateUpdated_whenDialogAnimatedIn() {
101         mFaceToFpView.onDialogAnimatedIn();
102         verify(mFaceToFpView.mFaceIconController)
103                 .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
104         verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt());
105     }
106 
107     @Test
testIconUpdatesState_whenDialogStateUpdated()108     public void testIconUpdatesState_whenDialogStateUpdated() {
109         mFaceToFpView.onDialogAnimatedIn();
110         verify(mFaceToFpView.mFaceIconController)
111                 .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
112         verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt());
113 
114         mFaceToFpView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
115         verify(mFaceToFpView.mFaceIconController).updateState(
116                 eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING),
117                 eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED));
118         verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt());
119 
120         assertEquals(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED, mFaceToFpView.mState);
121     }
122 
123     @Test
testStateUpdated_whenSwitchToFingerprint()124     public void testStateUpdated_whenSwitchToFingerprint() {
125         mFaceToFpView.onDialogAnimatedIn();
126         verify(mFaceToFpView.mFaceIconController)
127                 .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
128 
129         mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
130 
131         verify(mFaceToFpView.mFaceIconController).deactivate();
132         verify(mFaceToFpView.mUdfpsIconController).updateState(
133                 eq(AuthBiometricFaceToFingerprintView.STATE_IDLE));
134         verify(mConfirmButton).setVisibility(eq(View.GONE));
135 
136         mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
137 
138         verify(mFaceToFpView.mUdfpsIconController).updateState(
139                 eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
140     }
141 
142     @Test
testStateUpdated_whenSwitchToFingerprint_invokesCallbacks()143     public void testStateUpdated_whenSwitchToFingerprint_invokesCallbacks() {
144         class TestModalityListener implements ModalityListener {
145             public int switchCount = 0;
146 
147             @Override
148             public void onModalitySwitched(int oldModality, int newModality) {
149                 assertEquals(TYPE_FINGERPRINT, newModality);
150                 assertEquals(TYPE_FACE, oldModality);
151                 switchCount++;
152             }
153         }
154         final TestModalityListener modalityListener = new TestModalityListener();
155 
156         mFaceToFpView.onDialogAnimatedIn();
157         mFaceToFpView.setModalityListener(modalityListener);
158 
159         assertEquals(0, modalityListener.switchCount);
160 
161         mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
162         mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
163 
164         assertEquals(1, modalityListener.switchCount);
165     }
166 
167     @Test
testModeUpdated_onSoftError_whenSwitchToFingerprint()168     public void testModeUpdated_onSoftError_whenSwitchToFingerprint() {
169         mFaceToFpView.onDialogAnimatedIn();
170         mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face");
171         waitForIdleSync();
172 
173         verify(mIndicatorView).setText(
174                 eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
175         verify(mCallback).onAction(
176                 eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
177 
178         // First we enter the error state, since we need to show the error animation/text. The
179         // error state is later cleared based on a timer, and we enter STATE_AUTHENTICATING.
180         assertEquals(AuthBiometricFaceToFingerprintView.STATE_ERROR, mFaceToFpView.mState);
181     }
182 
183     @Test
testModeUpdated_onHardError_whenSwitchToFingerprint()184     public void testModeUpdated_onHardError_whenSwitchToFingerprint() {
185         mFaceToFpView.onDialogAnimatedIn();
186         mFaceToFpView.onError(TYPE_FACE, "oh no!");
187         waitForIdleSync();
188 
189         verify(mIndicatorView).setText(
190                 eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
191         verify(mCallback).onAction(
192                 eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
193 
194         // First we enter the error state, since we need to show the error animation/text. The
195         // error state is later cleared based on a timer, and we enter STATE_AUTHENTICATING.
196         assertEquals(AuthBiometricFaceToFingerprintView.STATE_ERROR, mFaceToFpView.mState);
197     }
198 
199     @Test
testFingerprintOnlyStartsOnFirstError()200     public void testFingerprintOnlyStartsOnFirstError() {
201         mFaceToFpView.onDialogAnimatedIn();
202         verify(mFaceToFpView.mFaceIconController)
203                 .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
204 
205         mFaceToFpView.onDialogAnimatedIn();
206         mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
207         mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
208 
209         reset(mCallback);
210 
211         mFaceToFpView.onError(TYPE_FACE, "oh no!");
212         mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face");
213 
214         verify(mCallback, never()).onAction(
215                 eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
216     }
217 
218     @Test
testOnSaveState()219     public void testOnSaveState() {
220         final FingerprintSensorPropertiesInternal sensorProps = createFingerprintSensorProps();
221         mFaceToFpView.setFingerprintSensorProps(sensorProps);
222 
223         final Bundle savedState = new Bundle();
224         mFaceToFpView.onSaveState(savedState);
225 
226         assertEquals(savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE),
227                 mFaceToFpView.getActiveSensorType());
228         assertEquals(savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS), sensorProps);
229     }
230 
231     @Test
testRestoreState()232     public void testRestoreState() {
233         final Bundle savedState = new Bundle();
234         savedState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FINGERPRINT);
235         savedState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS,
236                 createFingerprintSensorProps());
237 
238         mFaceToFpView.restoreState(savedState);
239 
240         assertEquals(mFaceToFpView.getActiveSensorType(), TYPE_FINGERPRINT);
241         assertTrue(mFaceToFpView.isFingerprintUdfps());
242     }
243 
244     @NonNull
createFingerprintSensorProps()245     private static FingerprintSensorPropertiesInternal createFingerprintSensorProps() {
246         final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
247         componentInfo.add(new ComponentInfoInternal("componentId", "hardwareVersion",
248                 "firmwareVersion", "serialNumber", "softwareVersion"));
249 
250         return new FingerprintSensorPropertiesInternal(
251                 0 /* sensorId */,
252                 SensorProperties.STRENGTH_STRONG,
253                 5 /* maxEnrollmentsPerUser */,
254                 componentInfo,
255                 FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
256                 true /* resetLockoutRequiresHardwareAuthToken */,
257                 540 /* sensorLocationX */,
258                 1600 /* sensorLocationY */,
259                 100 /* sensorRadius */);
260     }
261 
262     public class TestableView extends AuthBiometricFaceToFingerprintView {
TestableView(Context context)263         public TestableView(Context context) {
264             super(context, null, new MockInjector());
265         }
266 
267         @Override
getDelayAfterAuthenticatedDurationMs()268         protected int getDelayAfterAuthenticatedDurationMs() {
269             return 0;
270         }
271     }
272 
273     private class MockInjector extends AuthBiometricView.Injector {
274         @Override
getNegativeButton()275         public Button getNegativeButton() {
276             return mNegativeButton;
277         }
278 
279         @Override
getCancelButton()280         public Button getCancelButton() {
281             return mCancelButton;
282         }
283 
284         @Override
getUseCredentialButton()285         public Button getUseCredentialButton() {
286             return mUseCredentialButton;
287         }
288 
289         @Override
getConfirmButton()290         public Button getConfirmButton() {
291             return mConfirmButton;
292         }
293 
294         @Override
getTryAgainButton()295         public Button getTryAgainButton() {
296             return mTryAgainButton;
297         }
298 
299         @Override
getTitleView()300         public TextView getTitleView() {
301             return mTitleView;
302         }
303 
304         @Override
getSubtitleView()305         public TextView getSubtitleView() {
306             return mSubtitleView;
307         }
308 
309         @Override
getDescriptionView()310         public TextView getDescriptionView() {
311             return mDescriptionView;
312         }
313 
314         @Override
getIndicatorView()315         public TextView getIndicatorView() {
316             return mIndicatorView;
317         }
318 
319         @Override
getIconView()320         public ImageView getIconView() {
321             return mIconView;
322         }
323 
324         @Override
getIconHolderView()325         public View getIconHolderView() {
326             return mIconHolderView;
327         }
328 
329         @Override
getDelayAfterError()330         public int getDelayAfterError() {
331             return 0;
332         }
333 
334         @Override
getMediumToLargeAnimationDurationMs()335         public int getMediumToLargeAnimationDurationMs() {
336             return 0;
337         }
338     }
339 }
340