blob: 85d818ac8be348bc55356d6b82ab9fa331a9f40b [file] [log] [blame]
Kevin Chync53d9812019-07-30 18:10:30 -07001/*
2 * Copyright (C) 2019 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
17package com.android.systemui.biometrics;
18
19import static junit.framework.Assert.assertEquals;
Kevin Chyn050315f2019-08-08 14:22:54 -070020import static junit.framework.Assert.assertNull;
21import static junit.framework.TestCase.assertNotNull;
Kevin Chync53d9812019-07-30 18:10:30 -070022
23import static org.mockito.ArgumentMatchers.any;
Kevin Chyn050315f2019-08-08 14:22:54 -070024import static org.mockito.ArgumentMatchers.anyInt;
Kevin Chyn8429da22019-09-24 12:42:35 -070025import static org.mockito.ArgumentMatchers.anyString;
Kevin Chync53d9812019-07-30 18:10:30 -070026import static org.mockito.ArgumentMatchers.eq;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -070027import static org.mockito.Mockito.doAnswer;
Kevin Chync53d9812019-07-30 18:10:30 -070028import static org.mockito.Mockito.mock;
Kevin Chyn8429da22019-09-24 12:42:35 -070029import static org.mockito.Mockito.never;
Kevin Chync53d9812019-07-30 18:10:30 -070030import static org.mockito.Mockito.spy;
Kevin Chync53d9812019-07-30 18:10:30 -070031import static org.mockito.Mockito.verify;
32import static org.mockito.Mockito.when;
33
Kevin Chyn050315f2019-08-08 14:22:54 -070034import android.app.ActivityManager;
35import android.app.IActivityTaskManager;
36import android.content.ComponentName;
Dave Mankoffa5d8a392019-10-10 12:21:09 -040037import android.content.Context;
Kevin Chync53d9812019-07-30 18:10:30 -070038import android.content.pm.PackageManager;
39import android.content.res.Configuration;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -070040import android.hardware.biometrics.Authenticator;
Ilya Matyukhin0f9da352019-10-03 14:10:01 -070041import android.hardware.biometrics.BiometricAuthenticator;
Kevin Chyn8429da22019-09-24 12:42:35 -070042import android.hardware.biometrics.BiometricConstants;
Kevin Chync53d9812019-07-30 18:10:30 -070043import android.hardware.biometrics.BiometricPrompt;
44import android.hardware.biometrics.IBiometricServiceReceiverInternal;
Ilya Matyukhin0f9da352019-10-03 14:10:01 -070045import android.hardware.face.FaceManager;
Kevin Chync53d9812019-07-30 18:10:30 -070046import android.os.Bundle;
47import android.test.suitebuilder.annotation.SmallTest;
48import android.testing.AndroidTestingRunner;
49import android.testing.TestableContext;
50import android.testing.TestableLooper.RunWithLooper;
51
Ilya Matyukhin0f9da352019-10-03 14:10:01 -070052import com.android.internal.R;
Kevin Chync53d9812019-07-30 18:10:30 -070053import com.android.systemui.SysuiTestCase;
54import com.android.systemui.statusbar.CommandQueue;
55import com.android.systemui.statusbar.phone.StatusBar;
56
57import org.junit.Before;
58import org.junit.Test;
59import org.junit.runner.RunWith;
60import org.mockito.ArgumentCaptor;
61import org.mockito.Mock;
62import org.mockito.MockitoAnnotations;
63
Kevin Chyn050315f2019-08-08 14:22:54 -070064import java.util.ArrayList;
65import java.util.List;
66
Kevin Chync53d9812019-07-30 18:10:30 -070067@RunWith(AndroidTestingRunner.class)
68@RunWithLooper
69@SmallTest
Kevin Chynf8688a02019-08-27 17:04:05 -070070public class AuthControllerTest extends SysuiTestCase {
Kevin Chync53d9812019-07-30 18:10:30 -070071
72 @Mock
73 private PackageManager mPackageManager;
74 @Mock
75 private IBiometricServiceReceiverInternal mReceiver;
76 @Mock
Kevin Chynf8688a02019-08-27 17:04:05 -070077 private AuthDialog mDialog1;
Kevin Chync53d9812019-07-30 18:10:30 -070078 @Mock
Kevin Chynf8688a02019-08-27 17:04:05 -070079 private AuthDialog mDialog2;
Kevin Chync53d9812019-07-30 18:10:30 -070080
Kevin Chyn86f1b8e2019-09-24 19:00:49 -070081 private TestableAuthController mAuthController;
Kevin Chync53d9812019-07-30 18:10:30 -070082
83
84 @Before
85 public void setup() {
86 MockitoAnnotations.initMocks(this);
87
88 TestableContext context = spy(mContext);
89
90 mContext.putComponent(StatusBar.class, mock(StatusBar.class));
91 mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
92
93 when(context.getPackageManager()).thenReturn(mPackageManager);
94 when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE))
Ilya Matyukhin0f9da352019-10-03 14:10:01 -070095 .thenReturn(true);
Kevin Chync53d9812019-07-30 18:10:30 -070096 when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
Ilya Matyukhin0f9da352019-10-03 14:10:01 -070097 .thenReturn(true);
Kevin Chync53d9812019-07-30 18:10:30 -070098
Kevin Chyn050315f2019-08-08 14:22:54 -070099 when(mDialog1.getOpPackageName()).thenReturn("Dialog1");
100 when(mDialog2.getOpPackageName()).thenReturn("Dialog2");
101
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700102 when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
103 when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
Kevin Chync53d9812019-07-30 18:10:30 -0700104
Dave Mankoffa5d8a392019-10-10 12:21:09 -0400105 mAuthController = new TestableAuthController(context, new MockInjector());
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700106 mAuthController.mComponents = mContext.getComponents();
107
108 mAuthController.start();
Kevin Chync53d9812019-07-30 18:10:30 -0700109 }
110
111 // Callback tests
112
113 @Test
114 public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700115 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
116 mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
Kevin Chyn050315f2019-08-08 14:22:54 -0700117 verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
Kevin Chync53d9812019-07-30 18:10:30 -0700118 }
119
120 @Test
121 public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700122 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
123 mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
Kevin Chyn050315f2019-08-08 14:22:54 -0700124 verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
Kevin Chync53d9812019-07-30 18:10:30 -0700125 }
126
127 @Test
128 public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700129 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
130 mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
Kevin Chynff168dc2019-09-16 16:04:38 -0700131 verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
Kevin Chync53d9812019-07-30 18:10:30 -0700132 }
133
134 @Test
135 public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700136 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
137 mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
Kevin Chynff168dc2019-09-16 16:04:38 -0700138 verify(mReceiver).onDialogDismissed(
139 BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED);
Kevin Chync53d9812019-07-30 18:10:30 -0700140 }
141
142 @Test
143 public void testSendsReasonError_whenDismissedByError() throws Exception {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700144 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
145 mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR);
Kevin Chyn050315f2019-08-08 14:22:54 -0700146 verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
147 }
148
149 @Test
Kevin Chynff168dc2019-09-16 16:04:38 -0700150 public void testSendsReasonServerRequested_whenDismissedByServer() throws Exception {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700151 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
152 mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER);
Kevin Chyn050315f2019-08-08 14:22:54 -0700153 verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
Kevin Chync53d9812019-07-30 18:10:30 -0700154 }
155
Kevin Chynff168dc2019-09-16 16:04:38 -0700156 @Test
157 public void testSendsReasonCredentialConfirmed_whenDeviceCredentialAuthenticated()
158 throws Exception {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700159 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
160 mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
Kevin Chynff168dc2019-09-16 16:04:38 -0700161 verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED);
162 }
163
Kevin Chync53d9812019-07-30 18:10:30 -0700164 // Statusbar tests
165
166 @Test
167 public void testShowInvoked_whenSystemRequested()
168 throws Exception {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700169 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Kevin Chyn9cf89912019-08-30 13:33:58 -0700170 verify(mDialog1).show(any(), any());
Kevin Chync53d9812019-07-30 18:10:30 -0700171 }
172
173 @Test
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700174 public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
175 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700176 mAuthController.onBiometricAuthenticated();
Kevin Chyn050315f2019-08-08 14:22:54 -0700177 verify(mDialog1).onAuthenticationSucceeded();
Kevin Chync53d9812019-07-30 18:10:30 -0700178 }
179
180 @Test
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700181 public void testOnAuthenticationFailedInvoked_whenBiometricRejected() {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700182 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700183 mAuthController.onBiometricError(BiometricAuthenticator.TYPE_NONE,
184 BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
185 0 /* vendorCode */);
Kevin Chync53d9812019-07-30 18:10:30 -0700186
187 ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
Kevin Chyn050315f2019-08-08 14:22:54 -0700188 verify(mDialog1).onAuthenticationFailed(captor.capture());
Kevin Chync53d9812019-07-30 18:10:30 -0700189
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700190 assertEquals(captor.getValue(), mContext.getString(R.string.biometric_not_recognized));
191 }
192
193 @Test
194 public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
195 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
196 final int error = BiometricConstants.BIOMETRIC_ERROR_TIMEOUT;
197 final int vendorCode = 0;
198 mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
199
200 ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
201 verify(mDialog1).onAuthenticationFailed(captor.capture());
202
203 assertEquals(captor.getValue(), FaceManager.getErrorString(mContext, error, vendorCode));
Kevin Chync53d9812019-07-30 18:10:30 -0700204 }
205
206 @Test
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700207 public void testOnHelpInvoked_whenSystemRequested() {
208 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Kevin Chync53d9812019-07-30 18:10:30 -0700209 final String helpMessage = "help";
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700210 mAuthController.onBiometricHelp(helpMessage);
Kevin Chync53d9812019-07-30 18:10:30 -0700211
212 ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
Kevin Chyn050315f2019-08-08 14:22:54 -0700213 verify(mDialog1).onHelp(captor.capture());
Kevin Chync53d9812019-07-30 18:10:30 -0700214
215 assertEquals(captor.getValue(), helpMessage);
216 }
217
218 @Test
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700219 public void testOnErrorInvoked_whenSystemRequested() throws Exception {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700220 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Kevin Chyn8429da22019-09-24 12:42:35 -0700221 final int error = 1;
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700222 final int vendorCode = 0;
223 mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
Kevin Chync53d9812019-07-30 18:10:30 -0700224
225 ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
Kevin Chyn050315f2019-08-08 14:22:54 -0700226 verify(mDialog1).onError(captor.capture());
Kevin Chync53d9812019-07-30 18:10:30 -0700227
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700228 assertEquals(captor.getValue(), FaceManager.getErrorString(mContext, error, vendorCode));
Kevin Chync53d9812019-07-30 18:10:30 -0700229 }
230
231 @Test
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700232 public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() {
233 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Kevin Chyn8429da22019-09-24 12:42:35 -0700234 final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700235 final int vendorCode = 0;
Kevin Chyn8429da22019-09-24 12:42:35 -0700236
237 when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
238
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700239 mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
Kevin Chyn8429da22019-09-24 12:42:35 -0700240 verify(mDialog1, never()).onError(anyString());
241 verify(mDialog1).animateToCredentialUI();
242 }
243
244 @Test
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700245 public void testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI() {
246 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Kevin Chyn8429da22019-09-24 12:42:35 -0700247 final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700248 final int vendorCode = 0;
Kevin Chyn8429da22019-09-24 12:42:35 -0700249
250 when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
251
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700252 mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
Kevin Chyn8429da22019-09-24 12:42:35 -0700253 verify(mDialog1, never()).onError(anyString());
254 verify(mDialog1).animateToCredentialUI();
255 }
256
257 @Test
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700258 public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() {
259 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Kevin Chyn8429da22019-09-24 12:42:35 -0700260 final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700261 final int vendorCode = 0;
Kevin Chyn8429da22019-09-24 12:42:35 -0700262
263 when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
264
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700265 mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
266 verify(mDialog1).onError(eq(FaceManager.getErrorString(mContext, error, vendorCode)));
Kevin Chyn8429da22019-09-24 12:42:35 -0700267 verify(mDialog1, never()).animateToCredentialUI();
268 }
269
270 @Test
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700271 public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() {
272 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Kevin Chyn8429da22019-09-24 12:42:35 -0700273 final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700274 final int vendorCode = 0;
Kevin Chyn8429da22019-09-24 12:42:35 -0700275
276 when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
277
Ilya Matyukhin0f9da352019-10-03 14:10:01 -0700278 mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
279 verify(mDialog1).onError(eq(FaceManager.getErrorString(mContext, error, vendorCode)));
Kevin Chyn8429da22019-09-24 12:42:35 -0700280 verify(mDialog1, never()).animateToCredentialUI();
281 }
282
283 @Test
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700284 public void testDismissWithoutCallbackInvoked_whenSystemRequested() {
285 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
286 mAuthController.hideAuthenticationDialog();
Kevin Chyn050315f2019-08-08 14:22:54 -0700287 verify(mDialog1).dismissFromSystemServer();
288 }
Kevin Chync53d9812019-07-30 18:10:30 -0700289
Kevin Chyn050315f2019-08-08 14:22:54 -0700290 @Test
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700291 public void testClientNotified_whenDismissedBySystemServer() {
292 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
293 mAuthController.hideAuthenticationDialog();
Kevin Chyn050315f2019-08-08 14:22:54 -0700294 verify(mDialog1).dismissFromSystemServer();
295
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700296 assertNotNull(mAuthController.mCurrentDialog);
297 assertNotNull(mAuthController.mReceiver);
Kevin Chync53d9812019-07-30 18:10:30 -0700298 }
299
300 // Corner case tests
301
302 @Test
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700303 public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() {
304 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Kevin Chyn9cf89912019-08-30 13:33:58 -0700305 verify(mDialog1).show(any(), any());
Kevin Chync53d9812019-07-30 18:10:30 -0700306
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700307 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Kevin Chync53d9812019-07-30 18:10:30 -0700308
309 // First dialog should be dismissed without animation
Kevin Chyn050315f2019-08-08 14:22:54 -0700310 verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
Kevin Chync53d9812019-07-30 18:10:30 -0700311
312 // Second dialog should be shown without animation
Kevin Chyn9cf89912019-08-30 13:33:58 -0700313 verify(mDialog2).show(any(), any());
Kevin Chync53d9812019-07-30 18:10:30 -0700314 }
315
316 @Test
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700317 public void testConfigurationPersists_whenOnConfigurationChanged() {
318 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Kevin Chyn9cf89912019-08-30 13:33:58 -0700319 verify(mDialog1).show(any(), any());
Kevin Chync53d9812019-07-30 18:10:30 -0700320
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700321 // Return that the UI is in "showing" state
322 doAnswer(invocation -> {
323 Object[] args = invocation.getArguments();
324 Bundle savedState = (Bundle) args[0];
325 savedState.putInt(
326 AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING);
327 return null; // onSaveState returns void
328 }).when(mDialog1).onSaveState(any());
329
330 mAuthController.onConfigurationChanged(new Configuration());
Kevin Chync53d9812019-07-30 18:10:30 -0700331
332 ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
Kevin Chyn050315f2019-08-08 14:22:54 -0700333 verify(mDialog1).onSaveState(captor.capture());
Kevin Chync53d9812019-07-30 18:10:30 -0700334
335 // Old dialog doesn't animate
Kevin Chyn050315f2019-08-08 14:22:54 -0700336 verify(mDialog1).dismissWithoutCallback(eq(false /* animate */));
Kevin Chync53d9812019-07-30 18:10:30 -0700337
338 // Saved state is restored into new dialog
339 ArgumentCaptor<Bundle> captor2 = ArgumentCaptor.forClass(Bundle.class);
Kevin Chyn9cf89912019-08-30 13:33:58 -0700340 verify(mDialog2).show(any(), captor2.capture());
Kevin Chync53d9812019-07-30 18:10:30 -0700341
342 // TODO: This should check all values we want to save/restore
343 assertEquals(captor.getValue(), captor2.getValue());
344 }
345
Kevin Chyn050315f2019-08-08 14:22:54 -0700346 @Test
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700347 public void testConfigurationPersists_whenBiometricFallbackToCredential() {
348 showDialog(Authenticator.TYPE_CREDENTIAL | Authenticator.TYPE_BIOMETRIC,
349 BiometricPrompt.TYPE_FACE);
350 verify(mDialog1).show(any(), any());
351
352 // Pretend that the UI is now showing device credential UI.
353 doAnswer(invocation -> {
354 Object[] args = invocation.getArguments();
355 Bundle savedState = (Bundle) args[0];
356 savedState.putInt(
357 AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING);
358 savedState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, true);
359 return null; // onSaveState returns void
360 }).when(mDialog1).onSaveState(any());
361
362 mAuthController.onConfigurationChanged(new Configuration());
363
364 // Check that the new dialog was initialized to the credential UI.
365 ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
366 verify(mDialog2).show(any(), captor.capture());
367 assertEquals(Authenticator.TYPE_CREDENTIAL,
368 mAuthController.mLastBiometricPromptBundle
369 .getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
370 }
371
372 @Test
Kevin Chyn050315f2019-08-08 14:22:54 -0700373 public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700374 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
Kevin Chyn050315f2019-08-08 14:22:54 -0700375
376 List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
377 ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
378 taskInfo.topActivity = mock(ComponentName.class);
379 when(taskInfo.topActivity.getPackageName()).thenReturn("other_package");
380 tasks.add(taskInfo);
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700381 when(mAuthController.mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks);
Kevin Chyn050315f2019-08-08 14:22:54 -0700382
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700383 mAuthController.mTaskStackListener.onTaskStackChanged();
Kevin Chyn050315f2019-08-08 14:22:54 -0700384 waitForIdleSync();
385
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700386 assertNull(mAuthController.mCurrentDialog);
387 assertNull(mAuthController.mReceiver);
Kevin Chyn050315f2019-08-08 14:22:54 -0700388 verify(mDialog1).dismissWithoutCallback(true /* animate */);
389 verify(mReceiver).onDialogDismissed(eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL));
390 }
Kevin Chync53d9812019-07-30 18:10:30 -0700391
Curtis Belmonteffa9d872019-10-24 12:55:01 -0700392 @Test
393 public void testDoesNotCrash_whenTryAgainPressedAfterDismissal() {
394 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
395 mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
396 mAuthController.onTryAgainPressed();
397 }
398
399 @Test
400 public void testDoesNotCrash_whenDeviceCredentialPressedAfterDismissal() {
401 showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
402 mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
403 mAuthController.onDeviceCredentialPressed();
404 }
405
Kevin Chync53d9812019-07-30 18:10:30 -0700406 // Helpers
407
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700408 private void showDialog(int authenticators, int biometricModality) {
409 mAuthController.showAuthenticationDialog(createTestDialogBundle(authenticators),
Kevin Chync53d9812019-07-30 18:10:30 -0700410 mReceiver /* receiver */,
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700411 biometricModality,
Kevin Chync53d9812019-07-30 18:10:30 -0700412 true /* requireConfirmation */,
Kevin Chyn050315f2019-08-08 14:22:54 -0700413 0 /* userId */,
414 "testPackage");
Kevin Chync53d9812019-07-30 18:10:30 -0700415 }
416
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700417 private Bundle createTestDialogBundle(int authenticators) {
Kevin Chync53d9812019-07-30 18:10:30 -0700418 Bundle bundle = new Bundle();
419
420 bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title");
421 bundle.putCharSequence(BiometricPrompt.KEY_SUBTITLE, "Subtitle");
422 bundle.putCharSequence(BiometricPrompt.KEY_DESCRIPTION, "Description");
423 bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative Button");
424
425 // RequireConfirmation is a hint to BiometricService. This can be forced to be required
426 // by user settings, and should be tested in BiometricService.
427 bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true);
428
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700429 bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
430
Kevin Chync53d9812019-07-30 18:10:30 -0700431 return bundle;
432 }
433
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700434 private final class TestableAuthController extends AuthController {
Kevin Chync53d9812019-07-30 18:10:30 -0700435 private int mBuildCount = 0;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700436 private Bundle mLastBiometricPromptBundle;
Kevin Chync53d9812019-07-30 18:10:30 -0700437
Dave Mankoffa5d8a392019-10-10 12:21:09 -0400438 TestableAuthController(Context context, Injector injector) {
439 super(context, injector);
Kevin Chyn050315f2019-08-08 14:22:54 -0700440 }
441
Kevin Chync53d9812019-07-30 18:10:30 -0700442 @Override
Kevin Chynf8688a02019-08-27 17:04:05 -0700443 protected AuthDialog buildDialog(Bundle biometricPromptBundle,
Kevin Chynfc468262019-08-20 17:17:11 -0700444 boolean requireConfirmation, int userId, int type, String opPackageName,
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700445 boolean skipIntro) {
446
447 mLastBiometricPromptBundle = biometricPromptBundle;
448
Kevin Chynf8688a02019-08-27 17:04:05 -0700449 AuthDialog dialog;
Kevin Chync53d9812019-07-30 18:10:30 -0700450 if (mBuildCount == 0) {
451 dialog = mDialog1;
452 } else if (mBuildCount == 1) {
453 dialog = mDialog2;
454 } else {
455 dialog = null;
456 }
457 mBuildCount++;
458 return dialog;
459 }
460 }
Kevin Chyn050315f2019-08-08 14:22:54 -0700461
Kevin Chynf8688a02019-08-27 17:04:05 -0700462 private final class MockInjector extends AuthController.Injector {
Kevin Chyn050315f2019-08-08 14:22:54 -0700463 @Override
464 IActivityTaskManager getActivityTaskManager() {
465 return mock(IActivityTaskManager.class);
466 }
467 }
Kevin Chync53d9812019-07-30 18:10:30 -0700468}
469