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