1 /*
2  * Copyright (C) 2011 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.cts.verifier.security;
18 
19 import android.annotation.IntDef;
20 import android.annotation.StringRes;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.DialogFragment;
24 import android.app.Fragment;
25 import android.app.FragmentTransaction;
26 import android.app.KeyguardManager;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.res.Resources;
32 import android.hardware.biometrics.BiometricManager;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.HandlerThread;
36 import android.security.keystore.KeyGenParameterSpec;
37 import android.security.keystore.KeyProperties;
38 import android.util.Log;
39 import android.widget.Button;
40 
41 import com.android.cts.verifier.PassFailButtons;
42 import com.android.cts.verifier.R;
43 
44 import java.io.IOException;
45 import java.lang.annotation.Retention;
46 import java.lang.annotation.RetentionPolicy;
47 import java.security.InvalidAlgorithmParameterException;
48 import java.security.KeyStore;
49 import java.security.KeyStoreException;
50 import java.security.NoSuchAlgorithmException;
51 import java.security.NoSuchProviderException;
52 import java.security.cert.CertificateException;
53 
54 import javax.crypto.Cipher;
55 import javax.crypto.KeyGenerator;
56 import javax.crypto.SecretKey;
57 
58 /**
59  * This test verifies a key created with #setUnlockedDeviceRequired is not accessible when the
60  * device is locked.
61  * Requirements:
62  *   Pin / pattern / password must be set, and if the device supports biometric unlock this must be
63  *   set as well.
64  * Test flow:
65  *   1. Verify a pin / pattern / password has been configured; if the device supports biometric
66  *   unlock verify this has been configured as well. If not direct the user to Settings -> Security.
67  *   2. Prompt the user to lock the device and unlock with biometrics after 5 seconds.
68  *   3. Once notification of the SCREEN_OFF has been received verify the device is locked, then
69  *   verify the key cannot be accessed.
70  *   4. When the device is unlocked verify the key is now accessible after the biometric unlock.
71  *   5. Repeat steps 2-4, this time prompting the user to unlock using the device credentials.
72  */
73 public class UnlockedDeviceRequiredKeysTest extends PassFailButtons.Activity {
74     private static final String TAG = "UnlockedDeviceRequiredKeysTest";
75 
76     /**
77      * This tag is used to display and, when necessary, remove the dialog to display the current
78      * test status to the user.
79      */
80     private static final String FRAGMENT_TAG = "test_dialog";
81 
82     /** Alias for our key in the Android Key Store. */
83     private static final String KEY_NAME = "my_lock_key";
84     private static final byte[] SECRET_BYTE_ARRAY = new byte[]{1, 2, 3, 4, 5, 6};
85 
86     private Resources mResources;
87     private HandlerThread mHandlerThread;
88     private ScreenStateChangeReceiver mReceiver;
89 
90     private TestController mController;
91     private TestDialogFragment mDialogFragment;
92 
93     @Override
onCreate(Bundle savedInstanceState)94     protected void onCreate(Bundle savedInstanceState) {
95         super.onCreate(savedInstanceState);
96         setContentView(R.layout.sec_screen_lock_keys_main);
97         getPassButton().setEnabled(false);
98         setPassFailButtonClickListeners();
99         setInfoResources(R.string.sec_unlocked_device_required_keys_test,
100                 R.string.sec_unlocked_device_required_keys_test_info, -1);
101         mResources = getApplicationContext().getResources();
102 
103         // There are no broadcasts / notifications when a device state changes between locked and
104         // unlocked, but these two actions are most closely related to when the device should
105         // transition to a new lock state. Since the lock state may not immediately change when
106         // one of these broadcasts is sent use a HandlerThread to run off the UI thread to wait for
107         // the device to complete the transition to the new lock state.
108         mController = new TestController(this);
109         mReceiver = new ScreenStateChangeReceiver(mController);
110         IntentFilter intentFilter = new IntentFilter();
111         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
112         intentFilter.addAction(Intent.ACTION_USER_PRESENT);
113         mHandlerThread = new HandlerThread("receiver_thread");
114         mHandlerThread.start();
115         Handler handler = new Handler(mHandlerThread.getLooper());
116         registerReceiver(mReceiver, intentFilter, null, handler);
117 
118         // The test button should only be available when the DialogFragment is not currently
119         // displayed. When the button is clicked a new test is started (or the previous test
120         // resumed if it did not run through to completion).
121         mDialogFragment = TestDialogFragment.createDialogFragment(mController);
122         Button startTestButton = findViewById(R.id.sec_start_test_button);
123         startTestButton.setOnClickListener(view -> {
124             mController.updateTestState(true);
125             showDialog();
126         });
127     }
128 
129     /**
130      * Shows the dialog with the next steps required by the user, or a completion status if the
131      * test has finished.
132      */
showDialog()133     public void showDialog() {
134         // Remove any previously displayed fragments.
135         FragmentTransaction transaction = getFragmentManager().beginTransaction();
136         Fragment fragment = getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
137         if (fragment != null) {
138             transaction.remove(fragment);
139         }
140         transaction.addToBackStack(null);
141 
142         mDialogFragment = TestDialogFragment.createDialogFragment(mController);
143         mDialogFragment.show(transaction, FRAGMENT_TAG);
144     }
145 
146     /**
147      * Updates the text within the {@link DialogFragment}'s {@link AlertDialog} with the current
148      * state of the test and any required user actions.
149      */
updateDialogText()150     private void updateDialogText() {
151         Dialog dialog = mDialogFragment.getDialog();
152         if (dialog instanceof AlertDialog) {
153             ((AlertDialog) dialog).setMessage(mResources.getString(mController.getDialogMessage()));
154         }
155     }
156 
157     @Override
onResume()158     public void onResume() {
159         super.onResume();
160         // When the app is resumed update the dialog text to ensure the user is directed to the
161         // next required action.
162         updateDialogText();
163     }
164 
165     /**
166      * Creates a symmetric key in the Android Key Store which can only be used after the user has
167      * unlocked the device.
168      */
createKey()169     private static void createKey() {
170         // Generate a key to decrypt payment credentials, tokens, etc.
171         try {
172             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
173             keyStore.load(null);
174             KeyGenerator keyGenerator = KeyGenerator.getInstance(
175                     KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
176 
177             // Set the alias of the entry in Android KeyStore where the key will appear
178             // and the constraints (purposes) in the constructor of the Builder
179             keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
180                     KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
181                     .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
182                     .setUnlockedDeviceRequired(true)
183                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
184                     .build());
185             keyGenerator.generateKey();
186         } catch (NoSuchAlgorithmException | NoSuchProviderException
187                 | InvalidAlgorithmParameterException | KeyStoreException
188                 | CertificateException | IOException e) {
189             throw new RuntimeException("Failed to create a symmetric key", e);
190         }
191     }
192 
193     /**
194      * Tries to encrypt some data with the generated key in {@link #createKey} which
195      * only works if the user has unlocked the device.
196      *
197      * @param shouldFail boolean indicating whether an exception is expected; this is intended to
198      *                   prevent extra Logcat entries when the encrypt fails as expected
199      */
tryEncrypt(boolean shouldFail)200     private static boolean tryEncrypt(boolean shouldFail) {
201         try {
202             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
203             keyStore.load(null);
204             SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null);
205             Cipher cipher = Cipher.getInstance(
206                     KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/"
207                             + KeyProperties.ENCRYPTION_PADDING_PKCS7);
208 
209             // Try encrypting something, it will only work if the device is unlocked.
210             cipher.init(Cipher.ENCRYPT_MODE, secretKey);
211             cipher.doFinal(SECRET_BYTE_ARRAY);
212             return true;
213         } catch (Exception e) {
214             if (!shouldFail) {
215                 Log.w(TAG, "", e);
216             }
217             return false;
218         }
219     }
220 
221     /**
222      * {@link BroadcastReceiver} intended to receive notification when the device's lock state
223      * should be changing.
224      *
225      * <p>{@link Intent#ACTION_SCREEN_OFF} should be sent when the device's screen is shut off;
226      * shortly after this event the device should be locked. Similarly {@link
227      * Intent#ACTION_USER_PRESENT} should be sent when the device's screen is on, the device is
228      * unlocked, and the user should be present at the device. This receiver forwards the
229      * expected lock state change to the {@link TestController} to verify the device behaves as
230      * expected depending on the current state of the test.
231      */
232     private static class ScreenStateChangeReceiver extends BroadcastReceiver {
233         private TestController mController;
234 
235         /**
236          * Private constructor that accepts the {@code controller} that will be used to drive the
237          * test when lock state changes occur.
238          */
ScreenStateChangeReceiver(TestController controller)239         private ScreenStateChangeReceiver(TestController controller) {
240             mController = controller;
241         }
242 
243         /**
244          * Receives one of the registered broadcasts and sends the expected device state to the
245          * {@link TestController}.
246          */
247         @Override
onReceive(Context context, Intent intent)248         public void onReceive(Context context, Intent intent) {
249             switch (intent.getAction()) {
250                 case Intent.ACTION_SCREEN_OFF:
251                     mController.deviceStateChanged(true);
252                     break;
253                 case Intent.ACTION_USER_PRESENT:
254                     mController.deviceStateChanged(false);
255                     break;
256                 default:
257                     Log.w(TAG, "Ignoring unexpected broadcast: " + intent.getAction());
258             }
259         }
260     }
261 
262     /**
263      * Controls the flow of the test, verifying the prereqs are met and the device behaves as
264      * expected based on the current state of the test.
265      */
266     private static class TestController {
267         /**
268          * Number of times to retry the lock state query after a device has started the transition
269          * to a new lock state. This is intended to allow time for the device to enter the new state
270          * as returned by {@link KeyguardManager#isDeviceLocked()}.
271          */
272         private static final int MAX_DEVICE_STATE_RETRIES = 20;
273         /**
274          * Number of times to retry the encryption after successful unlock of the device.
275          */
276         private static final int MAX_ENCRYPT_RETRIES = 10;
277         /**
278          * Time to sleep between lock state queries, and encryption tries. This will allow the
279          * device up to one second for the new lock state change, and up to half a second for the
280          * KeyStore authentication.
281          */
282         private static final long DEVICE_STATE_SLEEP_TIME = 50;
283         /**
284          * The test has been initialized and is waiting to verify that the device has met the
285          * requirements for the test.
286          */
287         private static final int STATE_INITIALIZED = 0;
288         /**
289          * The test is waiting for the user to configure a pin / pattern / password and a biometric
290          * unlock (where applicable).
291          */
292         private static final int STATE_AWAITING_LOCK_SCREEN_CONFIG = 1;
293         /**
294          * The test is waiting for the user to configure a biometric unlock, but a pin / pattern /
295          * password is configured on the device.
296          */
297         private static final int STATE_AWAITING_BIOMETRIC_CONFIG = 2;
298         /**
299          * The test is waiting for the user to lock the device; after the lock the device should be
300          * unlocked via biometrics.
301          */
302         private static final int STATE_AWAITING_BIOMETRIC_LOCK = 3;
303         /**
304          * The test successfully verified the key was not available in the lock state; waiting for
305          * the user to unlock the device with biometrics to verify the key is available after the
306          * unlock.
307          */
308         private static final int STATE_BIOMETRIC_UNLOCK_COMPLETE = 4;
309         /**
310          * The test is waiting for the user to lock the device; after the lock the device should be
311          * unlocked via pin / pattern / password.
312          */
313         private static final int STATE_AWAITING_CREDENTIAL_LOCK = 5;
314         /**
315          * The test successfully verified the key was not available in the lock state; waiting for
316          * the user to unlock the device with the pin / pattern / password to verify the key is
317          * available after the unlock.
318          */
319         private static final int STATE_CREDENTIAL_UNLOCK_COMPLETE = 6;
320         /**
321          * The test failed since the key was available when the device was in the lock state.
322          */
323         private static final int STATE_FAILED_KEY_AVAILABLE_IN_LOCK_STATE = 7;
324         /**
325          * The test failed since the key was not available when the device was unlocked.
326          */
327         private static final int STATE_FAILED_KEY_NOT_AVAILABLE_IN_UNLOCKED_STATE = 8;
328         /**
329          * The test completed successfully.
330          */
331         private static final int STATE_TEST_SUCCESSFUL = 9;
332 
333         @IntDef(value = {
334                 STATE_INITIALIZED,
335                 STATE_AWAITING_LOCK_SCREEN_CONFIG,
336                 STATE_AWAITING_BIOMETRIC_CONFIG,
337                 STATE_AWAITING_BIOMETRIC_LOCK,
338                 STATE_BIOMETRIC_UNLOCK_COMPLETE,
339                 STATE_AWAITING_CREDENTIAL_LOCK,
340                 STATE_CREDENTIAL_UNLOCK_COMPLETE,
341                 STATE_FAILED_KEY_AVAILABLE_IN_LOCK_STATE,
342                 STATE_FAILED_KEY_NOT_AVAILABLE_IN_UNLOCKED_STATE,
343                 STATE_TEST_SUCCESSFUL,
344         })
345         @Retention(RetentionPolicy.SOURCE)
346         @interface TestState {
347         }
348 
349         private BiometricManager mBiometricManager;
350         private KeyguardManager mKeyguardManager;
351         private @TestState int mTestState;
352         private boolean mBiometricsSupported;
353         private UnlockedDeviceRequiredKeysTest mActivity;
354 
TestController(UnlockedDeviceRequiredKeysTest activity)355         private TestController(UnlockedDeviceRequiredKeysTest activity) {
356             mBiometricManager = activity.getSystemService(BiometricManager.class);
357             mKeyguardManager = activity.getSystemService(KeyguardManager.class);
358             // Initially assume biometrics are supported; when checking if test requirements are
359             // satisfied this can be set to false if the hardware is not available.
360             mBiometricsSupported = true;
361             mActivity = activity;
362         }
363 
364         /**
365          * Updates the current state of the test based on whether the test's requirements are met;
366          * if {@code startNewTest} is true this will also start a new test if the previous test
367          * reached a terminal state.
368          */
updateTestState(boolean startNewTest)369         private void updateTestState(boolean startNewTest) {
370             // If the test requirements are not met then return now as the verification process
371             // will set the appropriate test state based on what needs to be configured.
372             if (!verifyTestRequirements()) {
373                 return;
374             }
375             // If the test was just initialized, requirements just satisfied, or a terminal state
376             // was reached then update the state to the first applicable test to be performed.
377             @TestState int initialTestState = STATE_AWAITING_BIOMETRIC_LOCK;
378             if (!mBiometricsSupported) {
379                 initialTestState = STATE_AWAITING_CREDENTIAL_LOCK;
380             }
381             switch (mTestState) {
382                 case STATE_INITIALIZED:
383                 case STATE_AWAITING_LOCK_SCREEN_CONFIG:
384                 case STATE_AWAITING_BIOMETRIC_CONFIG:
385                     // When starting a new test recreate the key in case there are any problems
386                     // accessing the key on previous attempts.
387                     createKey();
388                     mTestState = initialTestState;
389                     break;
390                 case STATE_FAILED_KEY_AVAILABLE_IN_LOCK_STATE:
391                 case STATE_FAILED_KEY_NOT_AVAILABLE_IN_UNLOCKED_STATE:
392                 case STATE_TEST_SUCCESSFUL: {
393                     if (startNewTest) {
394                         mTestState = initialTestState;
395                     }
396                     break;
397                 }
398             }
399         }
400 
401         /**
402          * Called when the device should be entering a new lock state; verifies if the test is in
403          * a state where the new lock state is expected and if so runs the next portion of the test.
404          *
405          * @param enteringLockState boolean indicating whether the device should be entering a
406          *                          locked state
407          */
deviceStateChanged(boolean enteringLockState)408         private void deviceStateChanged(boolean enteringLockState) {
409             // The tests should only be run once the device meets the requirements.
410             if (verifyTestRequirements()) {
411                 // If the device is entering a lock state then run any of the awaiting lock tests.
412                 if (enteringLockState) {
413                     if (mTestState == STATE_AWAITING_BIOMETRIC_LOCK
414                             || mTestState == STATE_AWAITING_CREDENTIAL_LOCK) {
415                         runDeviceTest(enteringLockState);
416                     }
417                 } else {
418                     // else the device is entering an unlocked state; if a previous lock state was
419                     // verified then run the unlock test now.
420                     if (mTestState == STATE_BIOMETRIC_UNLOCK_COMPLETE ||
421                             mTestState == STATE_CREDENTIAL_UNLOCK_COMPLETE) {
422                         runDeviceTest(enteringLockState);
423                     }
424                 }
425             }
426             // Once the test has completed update the dialog's text to prompt the user for the next
427             // required action or to show the completion of the test.
428             mActivity.runOnUiThread(() -> mActivity.updateDialogText());
429             // Once the test completes successfully enable the pass button.
430             if (mTestState == STATE_TEST_SUCCESSFUL) {
431                 mActivity.runOnUiThread(() -> mActivity.getPassButton().setEnabled(true));
432             }
433         }
434 
435         /**
436          * Runs the next portion of the test based on the device's lock state.
437          *
438          * <p>This method will first wait for the device to reach the expected lock state since it
439          * can take some time from a lock state change event before the device is actually locked.
440          * If the test fails the lock state is verified again in case the user modified the lock
441          * state after the previous lock state was verified.
442          *
443          * @param enteringLockState boolean indicating whether the device should be entering a
444          *                          locked state.
445          */
runDeviceTest(boolean enteringLockState)446         private void runDeviceTest(boolean enteringLockState) {
447             // Wait for the device to reach the expected lock state before attempting the test.
448             if (waitForDeviceState(enteringLockState)) {
449                 boolean encryptSuccessful =
450                         verifyEncrypt(enteringLockState);
451                 // The test has failed if the encryption success is the same as the lock state; if
452                 // the device is being locked then the encryption should fail, and if the device is
453                 // being unlocked the encryption should be successful.
454                 if (encryptSuccessful == enteringLockState) {
455                     // The test was expected to fail; run one more check to ensure the device
456                     // is still in the expected lock state since it's possible the user locked /
457                     // unlocked the device after its state was previously verified.
458                     if (mKeyguardManager.isDeviceLocked() == enteringLockState) {
459                         // The test failed; set the appropriate failed state.
460                         if (enteringLockState) {
461                             mTestState = STATE_FAILED_KEY_AVAILABLE_IN_LOCK_STATE;
462                         } else {
463                             mTestState = STATE_FAILED_KEY_NOT_AVAILABLE_IN_UNLOCKED_STATE;
464                         }
465                     } else {
466                         Log.d(TAG, "Device state was changed while running test; need to"
467                                 + " retry the current test");
468                     }
469                 } else {
470                     // The current test passed; update the state to prompt the user for the next
471                     // event.
472                     if (enteringLockState) {
473                         if (mTestState == STATE_AWAITING_BIOMETRIC_LOCK) {
474                             mTestState = STATE_BIOMETRIC_UNLOCK_COMPLETE;
475                         } else {
476                             mTestState = STATE_CREDENTIAL_UNLOCK_COMPLETE;
477                         }
478                     } else {
479                         if (mTestState == STATE_BIOMETRIC_UNLOCK_COMPLETE) {
480                             mTestState = STATE_AWAITING_CREDENTIAL_LOCK;
481                         } else {
482                             mTestState = STATE_TEST_SUCCESSFUL;
483                         }
484                     }
485                 }
486             } else {
487                 Log.w(TAG, "The device did not reach the "
488                         + (enteringLockState ? "locked" : "unlocked")
489                         + " state within the timeout period; this may need to be increased for"
490                         + " future tests");
491             }
492         }
493 
494         /**
495          * Verify encryption tries reaches the expected state. Returns a boolean value whether the
496          * encryption was successful or not.
497          * In case KeyStore is not fully processed the device lock state change, this will allow
498          * half a second for retries, while returns immediately when reaches an expected state.
499          *
500          * @param shouldFail boolean indicating whether an expectation of the encryption is to fail
501          *                   or to success
502          */
verifyEncrypt(boolean shouldFail)503         private boolean verifyEncrypt(boolean shouldFail) {
504             int numRetries = 0;
505             boolean encryptSuccess = false;
506             while (numRetries < MAX_ENCRYPT_RETRIES) {
507                 numRetries++;
508 
509                 encryptSuccess = tryEncrypt(shouldFail);
510                 if (shouldFail != encryptSuccess) {
511                     Log.d(
512                             TAG,
513                             "The encryption got the expected result : "
514                                     + (encryptSuccess ? "success" : "fail"));
515                     return encryptSuccess;
516                 }
517 
518                 try {
519                     Thread.sleep(DEVICE_STATE_SLEEP_TIME);
520                 } catch (InterruptedException e) {
521                     Log.w(TAG, "Caught an Exception while sleeping: ", e);
522                 }
523             }
524             Log.e(
525                     TAG,
526                     "The encryption reached maximum retries, and got the unexpected result : "
527                             + (encryptSuccess ? "success" : "fail"));
528             return encryptSuccess;
529         }
530 
531         /**
532          * Waits for the lock state of the device as returned by {@link
533          * KeyguardManager#isDeviceLocked()} to match the expected state, returning {@code true} if
534          * the device reached the expected state within the timeout period.
535          */
waitForDeviceState(boolean enteringLockState)536         private boolean waitForDeviceState(boolean enteringLockState) {
537             int numRetries = 0;
538             while (numRetries < MAX_DEVICE_STATE_RETRIES) {
539                 numRetries++;
540                 boolean lockState = mKeyguardManager.isDeviceLocked();
541                 if (lockState == enteringLockState) {
542                     return true;
543                 }
544                 try {
545                     Thread.sleep(DEVICE_STATE_SLEEP_TIME);
546                 } catch (InterruptedException e) {
547                     Log.w(TAG, "Caught an Exception while sleeping: ", e);
548                 }
549             }
550             return false;
551         }
552 
553         /**
554          * Verifies the device meets the requirements that a pin / pattern / password is set and
555          * that a biometric unlock is configured where supported.
556          *
557          * <p>If the device does not meet the requirements for the test then the test state is
558          * updated to indicate the unmet requirements to ensure the user is prompted to resolve
559          * this.
560          *
561          * @return {@code true} if the device meets the requirements for the test
562          */
verifyTestRequirements()563         private boolean verifyTestRequirements() {
564             boolean requirementsMet = true;
565             // Check for biometric support at least once to ensure the user is not prompted to
566             // configure a biometric unlock if not supported by the device.
567             if (mBiometricsSupported) {
568                 int biometricResponse = mBiometricManager.canAuthenticate(
569                         BiometricManager.Authenticators.BIOMETRIC_STRONG);
570                 switch (biometricResponse) {
571                     // If the device does not have the hardware to support biometrics then set the
572                     // boolean to indicate the lack of support to prevent this check on future
573                     // invocations.
574                     case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
575                     case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
576                         mBiometricsSupported = false;
577                         break;
578                     // A success response indicates at least one biometric is registered for device
579                     // unlock.
580                     case BiometricManager.BIOMETRIC_SUCCESS:
581                         break;
582                     // A response of none enrolled indicates the device has the hardware to support
583                     // biometrics, but a biometric unlock has not yet been configured.
584                     case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
585                         mTestState = STATE_AWAITING_BIOMETRIC_CONFIG;
586                         requirementsMet = false;
587                         break;
588                     // Treat any other response as a biometric unlock still needs to be configured.
589                     default:
590                         mTestState = STATE_AWAITING_BIOMETRIC_CONFIG;
591                         Log.w(TAG,
592                                 "An unexpected response was received when querying "
593                                         + "BiometricManager#canAuthenticate: " + biometricResponse);
594                         requirementsMet = false;
595                         break;
596                 }
597             }
598             if (!mKeyguardManager.isDeviceSecure()) {
599                 mTestState = STATE_AWAITING_LOCK_SCREEN_CONFIG;
600                 requirementsMet = false;
601             }
602             return requirementsMet;
603         }
604 
605         /**
606          * Returns the dialog message that should be displayed to the user based on the current
607          * state of the test.
608          */
getDialogMessage()609         private @StringRes int getDialogMessage() {
610             // Update the test state in case it was recently initialized to ensure the next
611             // required user action is displayed.
612             updateTestState(false);
613             switch (mTestState) {
614                 case STATE_AWAITING_LOCK_SCREEN_CONFIG:
615                     if (mBiometricsSupported) {
616                         return R.string.unlock_req_config_lock_screen_and_biometrics;
617                     }
618                     return R.string.unlock_req_config_lock_screen;
619                 case STATE_AWAITING_BIOMETRIC_CONFIG:
620                     return R.string.unlock_req_config_biometrics;
621                 case STATE_AWAITING_BIOMETRIC_LOCK:
622                     return R.string.unlock_req_biometric_lock;
623                 case STATE_BIOMETRIC_UNLOCK_COMPLETE:
624                 case STATE_CREDENTIAL_UNLOCK_COMPLETE:
625                     return R.string.unlock_req_unlocked;
626                 case STATE_AWAITING_CREDENTIAL_LOCK:
627                     return R.string.unlock_req_credential_lock;
628                 case STATE_TEST_SUCCESSFUL:
629                     return R.string.unlock_req_successful;
630                 case STATE_FAILED_KEY_AVAILABLE_IN_LOCK_STATE:
631                     return R.string.unlock_req_failed_key_available_when_locked;
632                 case STATE_FAILED_KEY_NOT_AVAILABLE_IN_UNLOCKED_STATE:
633                     return R.string.unlock_req_failed_key_unavailable_when_unlocked;
634                 // While all states are accounted for a default return is required; report an
635                 // unknown state if this is reached to ensure the test is retried from a clean
636                 // state.
637                 default:
638                     return R.string.unlock_req_unknown_state;
639             }
640         }
641     }
642 
643     /**
644      * {@link DialogFragment} used to display an AlertDialog to guide the user through the steps
645      * required for the test. A {@code DialogFragment} is used since it automatically handles
646      * configuration changes.
647      */
648     public static class TestDialogFragment extends DialogFragment {
649         private TestController mController;
650 
651         /**
652          * Creates a new {@link DialogFragment} that can obtain its text from the provided {@code
653          * controller}.
654          */
createDialogFragment(TestController controller)655         private static TestDialogFragment createDialogFragment(TestController controller) {
656             TestDialogFragment fragment = new TestDialogFragment();
657             fragment.mController = controller;
658             return fragment;
659         }
660 
661         @Override
onCreateDialog(Bundle savedInstanceState)662         public Dialog onCreateDialog(Bundle savedInstanceState) {
663             return new AlertDialog.Builder(getContext()).setMessage(
664                     mController.getDialogMessage()).create();
665         }
666     }
667 }
668