• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 android.server.biometrics;
18 
19 import static android.os.PowerManager.FULL_WAKE_LOCK;
20 import static android.server.biometrics.util.SensorStates.SensorState;
21 import static android.server.biometrics.util.SensorStates.UserState;
22 
23 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
24 
25 import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_AUTH_IDLE;
26 import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_AUTH_PENDING_CONFIRM;
27 import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING;
28 import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_SHOWING_DEVICE_CREDENTIAL;
29 
30 import static com.google.common.truth.Truth.assertWithMessage;
31 
32 import static org.junit.Assert.assertEquals;
33 import static org.junit.Assert.assertNotNull;
34 import static org.junit.Assert.assertTrue;
35 import static org.junit.Assert.fail;
36 import static org.junit.Assume.assumeTrue;
37 import static org.mockito.Mockito.mock;
38 import static org.mockito.Mockito.verify;
39 
40 import android.app.Instrumentation;
41 import android.content.ComponentName;
42 import android.content.pm.PackageManager;
43 import android.graphics.Bitmap;
44 import android.hardware.biometrics.BiometricManager;
45 import android.hardware.biometrics.BiometricManager.Authenticators;
46 import android.hardware.biometrics.BiometricPrompt;
47 import android.hardware.biometrics.BiometricTestSession;
48 import android.hardware.biometrics.PromptContentView;
49 import android.hardware.biometrics.PromptVerticalListContentView;
50 import android.hardware.biometrics.SensorProperties;
51 import android.os.Bundle;
52 import android.os.CancellationSignal;
53 import android.os.Handler;
54 import android.os.Looper;
55 import android.os.PowerManager;
56 import android.platform.test.flag.junit.CheckFlagsRule;
57 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
58 import android.server.biometrics.util.BiometricCallbackHelper;
59 import android.server.biometrics.util.BiometricServiceState;
60 import android.server.biometrics.util.SensorStates;
61 import android.server.biometrics.util.TestSessionList;
62 import android.server.biometrics.util.Utils;
63 import android.server.wm.ActivityManagerTestBase;
64 import android.server.wm.TestJournalProvider.TestJournal;
65 import android.server.wm.UiDeviceUtils;
66 import android.server.wm.WindowManagerState;
67 import android.util.Log;
68 
69 import androidx.annotation.NonNull;
70 import androidx.annotation.Nullable;
71 import androidx.test.uiautomator.By;
72 import androidx.test.uiautomator.UiDevice;
73 import androidx.test.uiautomator.UiObject2;
74 import androidx.test.uiautomator.Until;
75 
76 import com.android.server.biometrics.nano.BiometricServiceStateProto;
77 
78 import org.junit.After;
79 import org.junit.Before;
80 import org.junit.Rule;
81 import org.mockito.ArgumentCaptor;
82 
83 import java.util.ArrayList;
84 import java.util.Collections;
85 import java.util.List;
86 import java.util.concurrent.Executor;
87 
88 /**
89  * Base class containing useful functionality. Actual tests should be done in subclasses.
90  */
91 abstract class BiometricTestBase extends ActivityManagerTestBase implements TestSessionList.Idler {
92 
93     @Rule
94     public final CheckFlagsRule mCheckFlagsRule =
95             DeviceFlagsValueProvider.createCheckFlagsRule();
96 
97     private static final String TAG = "BiometricTestBase";
98     private static final String DUMPSYS_BIOMETRIC = Utils.DUMPSYS_BIOMETRIC;
99     private static final String FLAG_CLEAR_SCHEDULER_LOG = " --clear-scheduler-buffer";
100 
101     // Negative-side (left) buttons
102     protected static final String BUTTON_ID_NEGATIVE = "button_negative";
103     protected static final String BUTTON_ID_USE_CREDENTIAL = "button_use_credential";
104 
105     // Positive-side (right) buttons
106     protected static final String BUTTON_ID_CONFIRM = "button_confirm";
107     protected static final String BUTTON_ID_TRY_AGAIN = "button_try_again";
108 
109     // Biometric text contents
110     protected static final String SCROLL_PARENT_VIEW = "scrollView";
111     protected static final String LOGO_VIEW = "logo";
112     protected static final String LOGO_DESCRIPTION_VIEW = "logo_description";
113     protected static final String TITLE_VIEW = "title";
114     protected static final String SUBTITLE_VIEW = "subtitle";
115     protected static final String DESCRIPTION_VIEW = "description";
116     protected static final String CONTENT_VIEW = "customized_view";
117     protected static final String CONTENT_DESCRIPTION_VIEW  = "customized_view_description";
118 
119     protected static final String VIEW_ID_PASSWORD_FIELD = "lockPassword";
120     protected static final String KEY_ENTER = "key_enter";
121     private static final int VIEW_WAIT_TIME_MS = 10000;
122     @NonNull protected Instrumentation mInstrumentation;
123     @NonNull protected BiometricManager mBiometricManager;
124     @NonNull protected List<SensorProperties> mSensorProperties;
125     @Nullable private PowerManager.WakeLock mWakeLock;
126     @NonNull protected UiDevice mDevice;
127     protected boolean mHasStrongBox;
128 
129     /**
130      * Expose this functionality to our package, since ActivityManagerTestBase's is `protected`.
131      * @param componentName
132      */
launchActivity(@onNull ComponentName componentName)133     void launchActivity(@NonNull ComponentName componentName) {
134         super.launchActivity(componentName);
135     }
136 
137     @Override
waitForIdleSensors()138     public void waitForIdleSensors() {
139         try {
140             Utils.waitForIdleService(this::getSensorStates);
141         } catch (Exception e) {
142             Log.e(TAG, "Exception when waiting for idle", e);
143         }
144     }
145 
146     /** @see Utils#getBiometricServiceCurrentState() */
147     @NonNull
getCurrentState()148     protected BiometricServiceState getCurrentState() throws Exception {
149         return Utils.getBiometricServiceCurrentState();
150     }
151 
152     @NonNull
getCurrentStateAndClearSchedulerLog()153     protected BiometricServiceState getCurrentStateAndClearSchedulerLog() throws Exception {
154         final byte[] dump = Utils.executeShellCommand(DUMPSYS_BIOMETRIC
155                 + FLAG_CLEAR_SCHEDULER_LOG);
156         final BiometricServiceStateProto proto = BiometricServiceStateProto.parseFrom(dump);
157         return BiometricServiceState.parseFrom(proto);
158     }
159 
160     @Nullable
findView(String id)161     protected UiObject2 findView(String id) {
162         Log.d(TAG, "Finding view: " + id);
163         return mDevice.findObject(By.res(mBiometricManager.getUiPackage(), id));
164     }
165 
166     @Nullable
findViewByText(String text)167     protected UiObject2 findViewByText(String text) {
168         Log.d(TAG, "Finding view by text: " + text);
169         return mDevice.findObject(By.text(text));
170     }
171 
172     @Nullable
waitForView(String id)173     protected UiObject2 waitForView(String id) {
174         Log.d(TAG, "Waiting for view " + id);
175         return mDevice.wait(Until.findObject(By.res(mBiometricManager.getUiPackage(), id)),
176                 VIEW_WAIT_TIME_MS);
177     }
178 
waitAndPressButton(String id)179     protected void waitAndPressButton(String id) {
180         final UiObject2 button = waitForView(id);
181         assertNotNull(button);
182         Log.d(TAG, "Waiting & clicking button: " + id);
183         button.click();
184     }
findAndPressButton(String id)185     protected void findAndPressButton(String id) {
186         final UiObject2 button = findView(id);
187         assertNotNull(button);
188         Log.d(TAG, "Clicking button: " + id);
189         button.click();
190     }
191 
getSensorStates()192     protected SensorStates getSensorStates() throws Exception {
193         return getCurrentState().mSensorStates;
194     }
195 
waitForState(@iometricServiceState.AuthSessionState int state)196     protected void waitForState(@BiometricServiceState.AuthSessionState int state)
197             throws Exception {
198         for (int i = 0; i < 20; i++) {
199             final BiometricServiceState serviceState = getCurrentState();
200             if (serviceState.mState != state) {
201                 Log.d(TAG, "Not in state " + state + " yet, current: " + serviceState.mState);
202                 Thread.sleep(300);
203             } else {
204                 return;
205             }
206         }
207         Log.d(TAG, "Timed out waiting for state to become: " + state);
208     }
209 
waitForStateNotEqual(@iometricServiceState.AuthSessionState int state)210     private void waitForStateNotEqual(@BiometricServiceState.AuthSessionState int state)
211             throws Exception {
212         for (int i = 0; i < 20; i++) {
213             final BiometricServiceState serviceState = getCurrentState();
214             if (serviceState.mState == state) {
215                 Log.d(TAG, "Not out of state yet, current: " + serviceState.mState);
216                 Thread.sleep(300);
217             } else {
218                 return;
219             }
220         }
221         Log.d(TAG, "Timed out waiting for state to not equal: " + state);
222     }
223 
anyEnrollmentsExist()224     private boolean anyEnrollmentsExist() throws Exception {
225         final BiometricServiceState serviceState = getCurrentState();
226 
227         for (SensorState sensorState : serviceState.mSensorStates.sensorStates.values()) {
228             for (UserState userState : sensorState.getUserStates().values()) {
229                 if (userState.numEnrolled != 0) {
230                     Log.d(TAG, "Enrollments still exist: " + serviceState);
231                     return true;
232                 }
233             }
234         }
235         return false;
236     }
237 
setUpNonConvenienceSensorEnrollment(SensorProperties props, BiometricTestSession session)238     protected void setUpNonConvenienceSensorEnrollment(SensorProperties props,
239             BiometricTestSession session) throws Exception {
240         waitForAllUnenrolled();
241         final int authenticatorStrength =
242                 Utils.testApiStrengthToAuthenticatorStrength(props.getSensorStrength());
243 
244         assertWithMessage("Sensor: " + props.getSensorId()
245                 + ", strength: " + props.getSensorStrength()).that(
246                 mBiometricManager.canAuthenticate(authenticatorStrength)).isEqualTo(
247                 BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED);
248 
249         enrollForSensor(session, props.getSensorId());
250 
251         assertWithMessage("Sensor: " + props.getSensorId()
252                 + ", strength: " + props.getSensorStrength()).that(
253                 mBiometricManager.canAuthenticate(authenticatorStrength)).isEqualTo(
254                 BiometricManager.BIOMETRIC_SUCCESS);
255     }
256 
successfullyAuthenticate(@onNull BiometricTestSession session, int userId)257     private void successfullyAuthenticate(@NonNull BiometricTestSession session, int userId)
258             throws Exception {
259         session.acceptAuthentication(userId);
260         mInstrumentation.waitForIdleSync();
261         waitForStateNotEqual(STATE_AUTH_STARTED_UI_SHOWING);
262         BiometricServiceState state = getCurrentState();
263         Log.d(TAG, "State after acceptAuthentication: " + state);
264         if (state.mState == STATE_AUTH_PENDING_CONFIRM) {
265             waitAndPressButton(BUTTON_ID_CONFIRM);
266             mInstrumentation.waitForIdleSync();
267             waitForState(STATE_AUTH_IDLE);
268         } else {
269             waitForState(STATE_AUTH_IDLE);
270         }
271 
272         assertEquals("Failed to become idle after authenticating",
273                 STATE_AUTH_IDLE, getCurrentState().mState);
274     }
275 
successfullyAuthenticate(@onNull BiometricTestSession session, int userId, TestJournal journal)276     protected void successfullyAuthenticate(@NonNull BiometricTestSession session, int userId,
277             TestJournal journal) throws Exception {
278         successfullyAuthenticate(session, userId);
279         mInstrumentation.waitForIdleSync();
280         BiometricCallbackHelper.State callbackState = getCallbackState(journal);
281         assertNotNull(callbackState);
282         assertEquals(callbackState.toString(), 1, callbackState.mNumAuthAccepted);
283         assertEquals(callbackState.toString(), 0, callbackState.mAcquiredReceived.size());
284         assertEquals(callbackState.toString(), 0, callbackState.mErrorsReceived.size());
285     }
286 
successfullyAuthenticate(@onNull BiometricTestSession session, int userId, BiometricPrompt.AuthenticationCallback callback)287     protected void successfullyAuthenticate(@NonNull BiometricTestSession session, int userId,
288             BiometricPrompt.AuthenticationCallback callback) throws Exception {
289         successfullyAuthenticate(session, userId);
290         ArgumentCaptor<BiometricPrompt.AuthenticationResult> resultCaptor =
291                 ArgumentCaptor.forClass(BiometricPrompt.AuthenticationResult.class);
292         verify(callback).onAuthenticationSucceeded(resultCaptor.capture());
293         assertEquals("Must be TYPE_BIOMETRIC",
294                 BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC,
295                 resultCaptor.getValue().getAuthenticationType());
296     }
297 
successfullyEnterCredential()298     protected void successfullyEnterCredential() throws Exception {
299         waitForState(STATE_SHOWING_DEVICE_CREDENTIAL);
300         BiometricServiceState state = getCurrentState();
301         assertTrue(state.toString(), state.mSensorStates.areAllSensorsIdle());
302         assertEquals(state.toString(), STATE_SHOWING_DEVICE_CREDENTIAL, state.mState);
303 
304         // Wait for any animations to complete. Ideally, this should be reflected in
305         // STATE_SHOWING_DEVICE_CREDENTIAL, but SysUI and BiometricService are different processes
306         // so we'd need to add some additional plumbing. We can improve this in the future.
307         // TODO(b/152240892)
308         Thread.sleep(1000);
309 
310         // Enter credential. AuthSession done, authentication callback received
311         final UiObject2 passwordField = findView(VIEW_ID_PASSWORD_FIELD);
312         Log.d(TAG, "Focusing, entering, submitting credential");
313         passwordField.click();
314         passwordField.setText(LOCK_CREDENTIAL);
315         if (mInstrumentation.getContext().getPackageManager().hasSystemFeature(
316                 PackageManager.FEATURE_AUTOMOTIVE)) {
317             final UiObject2 enterButton = findView(KEY_ENTER);
318             enterButton.click();
319         } else {
320             mDevice.pressEnter();
321         }
322         waitForState(STATE_AUTH_IDLE);
323 
324         state = getCurrentState();
325         assertEquals(state.toString(), STATE_AUTH_IDLE, state.mState);
326     }
327 
cancelAuthentication(@onNull CancellationSignal cancel)328     protected void cancelAuthentication(@NonNull CancellationSignal cancel) throws Exception {
329         cancel.cancel();
330         mInstrumentation.waitForIdleSync();
331         waitForState(STATE_AUTH_IDLE);
332 
333         //TODO(b/152240892): Currently BiometricService does not get a signal from SystemUI
334         //  when the dialog finishes animating away.
335         Thread.sleep(1000);
336 
337         BiometricServiceState state = getCurrentState();
338         assertEquals("Not idle after requesting cancellation", state.mState, STATE_AUTH_IDLE);
339     }
340 
waitForAllUnenrolled()341     protected void waitForAllUnenrolled() throws Exception {
342         for (int i = 0; i < 20; i++) {
343             if (anyEnrollmentsExist()) {
344                 Log.d(TAG, "Enrollments still exist..");
345                 Thread.sleep(300);
346             } else {
347                 return;
348             }
349         }
350         fail("Some sensors still have enrollments. State: " + getCurrentState());
351     }
352 
353     /**
354      * Shows a BiometricPrompt that specifies {@link Authenticators#DEVICE_CREDENTIAL}.
355      */
showCredentialOnlyBiometricPrompt( @onNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal, boolean shouldShow)356     protected void showCredentialOnlyBiometricPrompt(
357             @NonNull BiometricPrompt.AuthenticationCallback callback,
358             @NonNull CancellationSignal cancellationSignal,
359             boolean shouldShow) throws Exception {
360         showCredentialOnlyBiometricPromptWithContents(callback, cancellationSignal, shouldShow,
361                 "Title", "Subtitle", "Description", null /* contentView */);
362     }
363 
364     /**
365      * Shows a BiometricPrompt that specifies {@link Authenticators#DEVICE_CREDENTIAL}
366      * and the specified contents.
367      */
showCredentialOnlyBiometricPromptWithContents( @onNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal, boolean shouldShow, @NonNull String title, @NonNull String subtitle, @NonNull String description, @Nullable PromptContentView contentView)368     protected void showCredentialOnlyBiometricPromptWithContents(
369             @NonNull BiometricPrompt.AuthenticationCallback callback,
370             @NonNull CancellationSignal cancellationSignal, boolean shouldShow,
371             @NonNull String title, @NonNull String subtitle,
372             @NonNull String description, @Nullable PromptContentView contentView) throws Exception {
373         final Handler handler = new Handler(Looper.getMainLooper());
374         final Executor executor = handler::post;
375         final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext)
376                 .setTitle(title)
377                 .setSubtitle(subtitle)
378                 .setDescription(description)
379                 .setContentView(contentView)
380                 .setAllowedAuthenticators(Authenticators.DEVICE_CREDENTIAL)
381                 .setAllowBackgroundAuthentication(true)
382                 .build();
383 
384         prompt.authenticate(cancellationSignal, executor, callback);
385 
386         waitForCredentialIdle(shouldShow, contentView instanceof PromptVerticalListContentView);
387     }
388 
389     /**
390      * SHows a BiometricPrompt that sets
391      * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} to true.
392      */
showDeviceCredentialAllowedBiometricPrompt( @onNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal, boolean shouldShow)393     protected void showDeviceCredentialAllowedBiometricPrompt(
394             @NonNull BiometricPrompt.AuthenticationCallback callback,
395             @NonNull CancellationSignal cancellationSignal,
396             boolean shouldShow) throws Exception {
397         final Handler handler = new Handler(Looper.getMainLooper());
398         final Executor executor = handler::post;
399         final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext)
400                 .setTitle("Title")
401                 .setSubtitle("Subtitle")
402                 .setDescription("Description")
403                 .setDeviceCredentialAllowed(true)
404                 .setAllowBackgroundAuthentication(true)
405                 .build();
406 
407         prompt.authenticate(cancellationSignal, executor, callback);
408 
409         waitForCredentialIdle(shouldShow, false /* isContentViewWithMoreOptionsButton */);
410     }
411 
showDefaultBiometricPrompt(int sensorId, @NonNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal)412     protected BiometricPrompt showDefaultBiometricPrompt(int sensorId,
413             @NonNull BiometricPrompt.AuthenticationCallback callback,
414             @NonNull CancellationSignal cancellationSignal) throws Exception {
415         return showDefaultBiometricPromptWithLogo(sensorId, callback, cancellationSignal,
416                 -1 /* logoRes */, null /* logoBitmap */, "logo" /* logoDescription */);
417     }
418 
showDefaultBiometricPromptWithLogo(int sensorId, @NonNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal, int logoRes, Bitmap logoBitmap, String logoDescription)419     protected BiometricPrompt showDefaultBiometricPromptWithLogo(int sensorId,
420             @NonNull BiometricPrompt.AuthenticationCallback callback,
421             @NonNull CancellationSignal cancellationSignal, int logoRes, Bitmap logoBitmap,
422             String logoDescription) throws Exception {
423         final Handler handler = new Handler(Looper.getMainLooper());
424         final Executor executor = handler::post;
425         final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(mContext)
426                 .setTitle("Title")
427                 .setSubtitle("Subtitle")
428                 .setDescription("Description")
429                 .setConfirmationRequired(true)
430                 .setNegativeButton("Negative Button", executor, (dialog, which) -> {
431                     Log.d(TAG, "Negative button pressed");
432                 })
433                 .setAllowBackgroundAuthentication(true)
434                 .setAllowedSensorIds(new ArrayList<>(Collections.singletonList(sensorId)))
435                 .setLogoDescription(logoDescription);
436 
437         if (logoRes != -1) {
438             builder.setLogoRes(logoRes);
439         }
440         if (logoBitmap != null) {
441             builder.setLogoBitmap(logoBitmap);
442         }
443 
444         final BiometricPrompt prompt = builder.build();
445         prompt.authenticate(cancellationSignal, executor, callback);
446 
447         waitForState(STATE_AUTH_STARTED_UI_SHOWING);
448         return prompt;
449     }
450 
451     /**
452      * Shows the default BiometricPrompt (sensors meeting BIOMETRIC_WEAK) with a negative button,
453      * but does not complete authentication. In other words, the dialog will stay on the screen.
454      */
showDefaultBiometricPromptWithContents(int sensorId, int userId, boolean requireConfirmation, @NonNull BiometricPrompt.AuthenticationCallback callback, @NonNull String title, @NonNull String subtitle, @NonNull String description, @Nullable PromptContentView contentView, @NonNull String negativeButtonText)455     protected void showDefaultBiometricPromptWithContents(int sensorId, int userId,
456             boolean requireConfirmation, @NonNull BiometricPrompt.AuthenticationCallback callback,
457             @NonNull String title, @NonNull String subtitle, @NonNull String description,
458             @Nullable PromptContentView contentView, @NonNull String negativeButtonText)
459             throws Exception {
460         final Handler handler = new Handler(Looper.getMainLooper());
461         final Executor executor = handler::post;
462         final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext)
463                 .setTitle(title)
464                 .setSubtitle(subtitle)
465                 .setDescription(description)
466                 .setContentView(contentView)
467                 .setConfirmationRequired(requireConfirmation)
468                 .setNegativeButton(negativeButtonText, executor, (dialog, which) -> {
469                     Log.d(TAG, "Negative button pressed");
470                 })
471                 .setAllowBackgroundAuthentication(true)
472                 .setAllowedSensorIds(new ArrayList<>(Collections.singletonList(sensorId)))
473                 .build();
474         prompt.authenticate(new CancellationSignal(), executor, callback);
475 
476         waitForState(STATE_AUTH_STARTED_UI_SHOWING);
477     }
478 
479     /**
480      * Shows the default BiometricPrompt (sensors meeting BIOMETRIC_WEAK) with a negative button,
481      * and fakes successful authentication via TestApis.
482      */
showDefaultBiometricPromptAndAuth(@onNull BiometricTestSession session, int sensorId, int userId)483     protected void showDefaultBiometricPromptAndAuth(@NonNull BiometricTestSession session,
484             int sensorId, int userId) throws Exception {
485         BiometricPrompt.AuthenticationCallback callback = mock(
486                 BiometricPrompt.AuthenticationCallback.class);
487         showDefaultBiometricPromptWithContents(sensorId, userId, false /* requireConfirmation */,
488                 callback, "Title", "Subtitle", "Description",
489                 null, "Negative Button");
490         successfullyAuthenticate(session, userId);
491     }
492 
showBiometricPromptWithAuthenticators(int authenticators)493     protected BiometricPrompt showBiometricPromptWithAuthenticators(int authenticators) {
494         final Handler handler = new Handler(Looper.getMainLooper());
495         final Executor executor = handler::post;
496         final BiometricPrompt.Builder promptBuilder = new BiometricPrompt.Builder(mContext)
497                 .setTitle("Title")
498                 .setSubtitle("Subtitle")
499                 .setDescription("Description")
500                 .setAllowBackgroundAuthentication(true)
501                 .setAllowedAuthenticators(authenticators);
502         if ((authenticators & Authenticators.DEVICE_CREDENTIAL) == 0) {
503             promptBuilder.setNegativeButton("Negative Button", executor, (dialog, which) -> {
504                 Log.d(TAG, "Negative button pressed");
505             });
506         }
507 
508         final BiometricPrompt prompt = promptBuilder.build();
509         prompt.authenticate(new CancellationSignal(), executor,
510                 new BiometricPrompt.AuthenticationCallback() {
511                     @Override
512                     public void onAuthenticationError(int errorCode, CharSequence errString) {
513                         Log.d(TAG, "onAuthenticationError: " + errorCode);
514                     }
515 
516                     @Override
517                     public void onAuthenticationSucceeded(
518                             BiometricPrompt.AuthenticationResult result) {
519                         Log.d(TAG, "onAuthenticationSucceeded");
520                     }
521                 });
522         return prompt;
523     }
524 
launchActivityAndWaitForResumed(@onNull ActivitySession activitySession)525     protected void launchActivityAndWaitForResumed(@NonNull ActivitySession activitySession) {
526         activitySession.start();
527         mWmState.waitForActivityState(activitySession.getComponentName(),
528                 WindowManagerState.STATE_RESUMED);
529         mInstrumentation.waitForIdleSync();
530     }
531 
closeActivity(@onNull ActivitySession activitySession)532     protected void closeActivity(@NonNull ActivitySession activitySession) throws Exception {
533         activitySession.close();
534         mInstrumentation.waitForIdleSync();
535     }
536 
getCurrentStrength(int sensorId)537     protected int getCurrentStrength(int sensorId) throws Exception {
538         final BiometricServiceState serviceState = getCurrentState();
539         return serviceState.mSensorStates.sensorStates.get(sensorId).getCurrentStrength();
540     }
541 
getSensorsOfTargetStrength(int targetStrength)542     protected List<Integer> getSensorsOfTargetStrength(int targetStrength) {
543         final List<Integer> sensors = new ArrayList<>();
544         for (SensorProperties prop : mSensorProperties) {
545             if (prop.getSensorStrength() == targetStrength) {
546                 sensors.add(prop.getSensorId());
547             }
548         }
549         Log.d(TAG, "getSensorsOfTargetStrength: num of target sensors=" + sensors.size());
550         return sensors;
551     }
552 
553     @NonNull
getCallbackState(@onNull TestJournal journal)554     protected static BiometricCallbackHelper.State getCallbackState(@NonNull TestJournal journal) {
555         Utils.waitFor("Waiting for authentication callback",
556                 () -> journal.extras.containsKey(BiometricCallbackHelper.KEY),
557                 (lastResult) -> fail("authentication callback never received - died waiting"));
558 
559         final Bundle bundle = journal.extras.getBundle(BiometricCallbackHelper.KEY);
560         final BiometricCallbackHelper.State state =
561                 BiometricCallbackHelper.State.fromBundle(bundle);
562 
563         // Clear the extras since we want to wait for the journal to sync any new info the next
564         // time it's read
565         journal.extras.clear();
566 
567         return state;
568     }
569 
570     @Before
setUp()571     public void setUp() throws Exception {
572         mInstrumentation = getInstrumentation();
573         mBiometricManager = mInstrumentation.getContext().getSystemService(BiometricManager.class);
574 
575         mInstrumentation.getUiAutomation().adoptShellPermissionIdentity();
576         mDevice = UiDevice.getInstance(mInstrumentation);
577         mSensorProperties = mBiometricManager.getSensorProperties();
578 
579         assumeTrue(mInstrumentation.getContext().getPackageManager().hasSystemFeature(
580                 PackageManager.FEATURE_SECURE_LOCK_SCREEN));
581 
582         mHasStrongBox = mContext.getPackageManager().hasSystemFeature(
583                 PackageManager.FEATURE_STRONGBOX_KEYSTORE);
584 
585         // Keep the screen on for the duration of each test, since BiometricPrompt goes away
586         // when screen turns off.
587         final PowerManager pm = mInstrumentation.getContext().getSystemService(PowerManager.class);
588         mWakeLock = pm.newWakeLock(FULL_WAKE_LOCK, TAG);
589         mWakeLock.acquire();
590 
591         // Turn screen on and dismiss keyguard
592         UiDeviceUtils.pressWakeupButton();
593         UiDeviceUtils.pressUnlockButton();
594     }
595 
596     @After
cleanup()597     public void cleanup() {
598         mInstrumentation.waitForIdleSync();
599 
600         // Authentication lifecycle is done
601         waitForIdleSensors();
602 
603         if (mWakeLock != null) {
604             mWakeLock.release();
605         }
606         mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
607     }
608 
enrollForSensor(@onNull BiometricTestSession session, int sensorId)609     protected void enrollForSensor(@NonNull BiometricTestSession session, int sensorId)
610             throws Exception {
611         Log.d(TAG, "Enrolling for sensor: " + sensorId);
612         final int userId = Utils.getUserId();
613 
614         session.startEnroll(userId);
615         mInstrumentation.waitForIdleSync();
616         Utils.waitForBusySensor(sensorId, this::getSensorStates);
617 
618         //Wait for enrollment operation in biometrics sensor to be complete before
619         //retrieving enrollment results. The operation takes a little time especically
620         //on Cutterfish where multiple biometric operations must be completed during
621         //the enrollent
622         //TODO(b/217275524)
623         Thread.sleep(200);
624         session.finishEnroll(userId);
625         mInstrumentation.waitForIdleSync();
626         Utils.waitForIdleService(this::getSensorStates);
627 
628         final BiometricServiceState state = getCurrentState();
629         assertEquals("Sensor: " + sensorId + " should have exactly one enrollment",
630                 1, state.mSensorStates.sensorStates
631                         .get(sensorId).getUserStates().get(userId).numEnrolled);
632     }
633 
waitForCredentialIdle(boolean shouldShow, boolean isVerticalContentView)634     protected void waitForCredentialIdle(boolean shouldShow,
635             boolean isVerticalContentView)
636             throws Exception {
637         boolean shouldShowBpWithoutIconForCredential = Utils.shouldShowBpWithoutIconForCredential(
638                 isVerticalContentView, false /*isBiometricAllowed*/);
639         mInstrumentation.waitForIdleSync();
640 
641         if (shouldShow) {
642             if (shouldShowBpWithoutIconForCredential) {
643                 waitForState(STATE_AUTH_STARTED_UI_SHOWING);
644                 findAndPressButton(BUTTON_ID_USE_CREDENTIAL);
645                 waitForState(STATE_SHOWING_DEVICE_CREDENTIAL);
646             }
647             // Wait for any animations to complete. Ideally, this should be reflected in
648             // STATE_SHOWING_DEVICE_CREDENTIAL, but SysUI and BiometricService are different
649             // processes so we'd need to add some additional plumbing. We can improve this in the
650             // future.
651             // TODO(b/152240892)
652             Thread.sleep(1000);
653             waitForState(STATE_SHOWING_DEVICE_CREDENTIAL);
654             BiometricServiceState state = getCurrentState();
655             assertEquals(state.toString(), STATE_SHOWING_DEVICE_CREDENTIAL, state.mState);
656         } else {
657             Utils.waitForIdleService(this::getSensorStates);
658         }
659     }
660 }
661