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 UnlockedDeviceRequiredTest extends PassFailButtons.Activity {
74     private static final String TAG = "UnlockedDeviceRequiredTest";
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_test,
100                 R.string.sec_unlocked_device_required_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          * Time to sleep between lock state queries; this will allow the device up to one second to
275          * reach the new lock state before timing out.
276          */
277         private static final long DEVICE_STATE_SLEEP_TIME = 50;
278 
279         /**
280          * The test has been initialized and is waiting to verify that the device has met the
281          * requirements for the test.
282          */
283         private static final int STATE_INITIALIZED = 0;
284         /**
285          * The test is waiting for the user to configure a pin / pattern / password and a biometric
286          * unlock (where applicable).
287          */
288         private static final int STATE_AWAITING_LOCK_SCREEN_CONFIG = 1;
289         /**
290          * The test is waiting for the user to configure a biometric unlock, but a pin / pattern /
291          * password is configured on the device.
292          */
293         private static final int STATE_AWAITING_BIOMETRIC_CONFIG = 2;
294         /**
295          * The test is waiting for the user to lock the device; after the lock the device should be
296          * unlocked via biometrics.
297          */
298         private static final int STATE_AWAITING_BIOMETRIC_LOCK = 3;
299         /**
300          * The test successfully verified the key was not available in the lock state; waiting for
301          * the user to unlock the device with biometrics to verify the key is available after the
302          * unlock.
303          */
304         private static final int STATE_BIOMETRIC_UNLOCK_COMPLETE = 4;
305         /**
306          * The test is waiting for the user to lock the device; after the lock the device should be
307          * unlocked via pin / pattern / password.
308          */
309         private static final int STATE_AWAITING_CREDENTIAL_LOCK = 5;
310         /**
311          * The test successfully verified the key was not available in the lock state; waiting for
312          * the user to unlock the device with the pin / pattern / password to verify the key is
313          * available after the unlock.
314          */
315         private static final int STATE_CREDENTIAL_UNLOCK_COMPLETE = 6;
316         /**
317          * The test failed since the key was available when the device was in the lock state.
318          */
319         private static final int STATE_FAILED_KEY_AVAILABLE_IN_LOCK_STATE = 7;
320         /**
321          * The test failed since the key was not available when the device was unlocked.
322          */
323         private static final int STATE_FAILED_KEY_NOT_AVAILABLE_IN_UNLOCKED_STATE = 8;
324         /**
325          * The test completed successfully.
326          */
327         private static final int STATE_TEST_SUCCESSFUL = 9;
328 
329         @IntDef(value = {
330                 STATE_INITIALIZED,
331                 STATE_AWAITING_LOCK_SCREEN_CONFIG,
332                 STATE_AWAITING_BIOMETRIC_CONFIG,
333                 STATE_AWAITING_BIOMETRIC_LOCK,
334                 STATE_BIOMETRIC_UNLOCK_COMPLETE,
335                 STATE_AWAITING_CREDENTIAL_LOCK,
336                 STATE_CREDENTIAL_UNLOCK_COMPLETE,
337                 STATE_FAILED_KEY_AVAILABLE_IN_LOCK_STATE,
338                 STATE_FAILED_KEY_NOT_AVAILABLE_IN_UNLOCKED_STATE,
339                 STATE_TEST_SUCCESSFUL,
340         })
341         @Retention(RetentionPolicy.SOURCE)
342         @interface TestState {
343         }
344 
345         private BiometricManager mBiometricManager;
346         private KeyguardManager mKeyguardManager;
347         private @TestState int mTestState;
348         private boolean mBiometricsSupported;
349         private UnlockedDeviceRequiredTest mActivity;
350 
TestController(UnlockedDeviceRequiredTest activity)351         private TestController(UnlockedDeviceRequiredTest activity) {
352             mBiometricManager = activity.getSystemService(BiometricManager.class);
353             mKeyguardManager = activity.getSystemService(KeyguardManager.class);
354             // Initially assume biometrics are supported; when checking if test requirements are
355             // satisfied this can be set to false if the hardware is not available.
356             mBiometricsSupported = true;
357             mActivity = activity;
358         }
359 
360         /**
361          * Updates the current state of the test based on whether the test's requirements are met;
362          * if {@code startNewTest} is true this will also start a new test if the previous test
363          * reached a terminal state.
364          */
updateTestState(boolean startNewTest)365         private void updateTestState(boolean startNewTest) {
366             // If the test requirements are not met then return now as the verification process
367             // will set the appropriate test state based on what needs to be configured.
368             if (!verifyTestRequirements()) {
369                 return;
370             }
371             // If the test was just initialized, requirements just satisfied, or a terminal state
372             // was reached then update the state to the first applicable test to be performed.
373             @TestState int initialTestState = STATE_AWAITING_BIOMETRIC_LOCK;
374             if (!mBiometricsSupported) {
375                 initialTestState = STATE_AWAITING_CREDENTIAL_LOCK;
376             }
377             switch (mTestState) {
378                 case STATE_INITIALIZED:
379                 case STATE_AWAITING_LOCK_SCREEN_CONFIG:
380                 case STATE_AWAITING_BIOMETRIC_CONFIG:
381                     // When starting a new test recreate the key in case there are any problems
382                     // accessing the key on previous attempts.
383                     createKey();
384                     mTestState = initialTestState;
385                     break;
386                 case STATE_FAILED_KEY_AVAILABLE_IN_LOCK_STATE:
387                 case STATE_FAILED_KEY_NOT_AVAILABLE_IN_UNLOCKED_STATE:
388                 case STATE_TEST_SUCCESSFUL: {
389                     if (startNewTest) {
390                         mTestState = initialTestState;
391                     }
392                     break;
393                 }
394             }
395         }
396 
397         /**
398          * Called when the device should be entering a new lock state; verifies if the test is in
399          * a state where the new lock state is expected and if so runs the next portion of the test.
400          *
401          * @param enteringLockState boolean indicating whether the device should be entering a
402          *                          locked state
403          */
deviceStateChanged(boolean enteringLockState)404         private void deviceStateChanged(boolean enteringLockState) {
405             // The tests should only be run once the device meets the requirements.
406             if (verifyTestRequirements()) {
407                 // If the device is entering a lock state then run any of the awaiting lock tests.
408                 if (enteringLockState) {
409                     if (mTestState == STATE_AWAITING_BIOMETRIC_LOCK
410                             || mTestState == STATE_AWAITING_CREDENTIAL_LOCK) {
411                         runDeviceTest(enteringLockState);
412                     }
413                 } else {
414                     // else the device is entering an unlocked state; if a previous lock state was
415                     // verified then run the unlock test now.
416                     if (mTestState == STATE_BIOMETRIC_UNLOCK_COMPLETE ||
417                             mTestState == STATE_CREDENTIAL_UNLOCK_COMPLETE) {
418                         runDeviceTest(enteringLockState);
419                     }
420                 }
421             }
422             // Once the test has completed update the dialog's text to prompt the user for the next
423             // required action or to show the completion of the test.
424             mActivity.runOnUiThread(() -> mActivity.updateDialogText());
425             // Once the test completes successfully enable the pass button.
426             if (mTestState == STATE_TEST_SUCCESSFUL) {
427                 mActivity.runOnUiThread(() -> mActivity.getPassButton().setEnabled(true));
428             }
429         }
430 
431         /**
432          * Runs the next portion of the test based on the device's lock state.
433          *
434          * <p>This method will first wait for the device to reach the expected lock state since it
435          * can take some time from a lock state change event before the device is actually locked.
436          * If the test fails the lock state is verified again in case the user modified the lock
437          * state after the previous lock state was verified.
438          *
439          * @param enteringLockState boolean indicating whether the device should be entering a
440          *                          locked state.
441          */
runDeviceTest(boolean enteringLockState)442         private void runDeviceTest(boolean enteringLockState) {
443             // Wait for the device to reach the expected lock state before attempting the test.
444             if (waitForDeviceState(enteringLockState)) {
445                 boolean encryptSuccessful = tryEncrypt(enteringLockState);
446                 // The test has failed if the encryption success is the same as the lock state; if
447                 // the device is being locked then the encryption should fail, and if the device is
448                 // being unlocked the encryption should be successful.
449                 if (encryptSuccessful == enteringLockState) {
450                     // The test was expected to fail; run one more check to ensure the device
451                     // is still in the expected lock state since it's possible the user locked /
452                     // unlocked the device after its state was previously verified.
453                     if (mKeyguardManager.isDeviceLocked() == enteringLockState) {
454                         // The test failed; set the appropriate failed state.
455                         if (enteringLockState) {
456                             mTestState = STATE_FAILED_KEY_AVAILABLE_IN_LOCK_STATE;
457                         } else {
458                             mTestState = STATE_FAILED_KEY_NOT_AVAILABLE_IN_UNLOCKED_STATE;
459                         }
460                     } else {
461                         Log.d(TAG, "Device state was changed while running test; need to"
462                                 + " retry the current test");
463                     }
464                 } else {
465                     // The current test passed; update the state to prompt the user for the next
466                     // event.
467                     if (enteringLockState) {
468                         if (mTestState == STATE_AWAITING_BIOMETRIC_LOCK) {
469                             mTestState = STATE_BIOMETRIC_UNLOCK_COMPLETE;
470                         } else {
471                             mTestState = STATE_CREDENTIAL_UNLOCK_COMPLETE;
472                         }
473                     } else {
474                         if (mTestState == STATE_BIOMETRIC_UNLOCK_COMPLETE) {
475                             mTestState = STATE_AWAITING_CREDENTIAL_LOCK;
476                         } else {
477                             mTestState = STATE_TEST_SUCCESSFUL;
478                         }
479                     }
480                 }
481             } else {
482                 Log.w(TAG, "The device did not reach the "
483                         + (enteringLockState ? "locked" : "unlocked")
484                         + " state within the timeout period; this may need to be increased for"
485                         + " future tests");
486             }
487         }
488 
489         /**
490          * Waits for the lock state of the device as returned by {@link
491          * KeyguardManager#isDeviceLocked()} to match the expected state, returning {@code true} if
492          * the device reached the expected state within the timeout period.
493          */
waitForDeviceState(boolean enteringLockState)494         private boolean waitForDeviceState(boolean enteringLockState) {
495             int numRetries = 0;
496             while (numRetries < MAX_DEVICE_STATE_RETRIES) {
497                 numRetries++;
498                 boolean lockState = mKeyguardManager.isDeviceLocked();
499                 if (lockState == enteringLockState) {
500                     return true;
501                 }
502                 try {
503                     Thread.sleep(DEVICE_STATE_SLEEP_TIME);
504                 } catch (InterruptedException e) {
505                     Log.w(TAG, "Caught an Exception while sleeping: ", e);
506                 }
507             }
508             return false;
509         }
510 
511         /**
512          * Verifies the device meets the requirements that a pin / pattern / password is set and
513          * that a biometric unlock is configured where supported.
514          *
515          * <p>If the device does not meet the requirements for the test then the test state is
516          * updated to indicate the unmet requirements to ensure the user is prompted to resolve
517          * this.
518          *
519          * @return {@code true} if the device meets the requirements for the test
520          */
verifyTestRequirements()521         private boolean verifyTestRequirements() {
522             boolean requirementsMet = true;
523             // Check for biometric support at least once to ensure the user is not prompted to
524             // configure a biometric unlock if not supported by the device.
525             if (mBiometricsSupported) {
526                 int biometricResponse = mBiometricManager.canAuthenticate(
527                         BiometricManager.Authenticators.BIOMETRIC_STRONG);
528                 switch (biometricResponse) {
529                     // If the device does not have the hardware to support biometrics then set the
530                     // boolean to indicate the lack of support to prevent this check on future
531                     // invocations.
532                     case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
533                     case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
534                         mBiometricsSupported = false;
535                         break;
536                     // A success response indicates at least one biometric is registered for device
537                     // unlock.
538                     case BiometricManager.BIOMETRIC_SUCCESS:
539                         break;
540                     // A response of none enrolled indicates the device has the hardware to support
541                     // biometrics, but a biometric unlock has not yet been configured.
542                     case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
543                         mTestState = STATE_AWAITING_BIOMETRIC_CONFIG;
544                         requirementsMet = false;
545                         break;
546                     // Treat any other response as a biometric unlock still needs to be configured.
547                     default:
548                         mTestState = STATE_AWAITING_BIOMETRIC_CONFIG;
549                         Log.w(TAG,
550                                 "An unexpected response was received when querying "
551                                         + "BiometricManager#canAuthenticate: " + biometricResponse);
552                         requirementsMet = false;
553                         break;
554                 }
555             }
556             if (!mKeyguardManager.isDeviceSecure()) {
557                 mTestState = STATE_AWAITING_LOCK_SCREEN_CONFIG;
558                 requirementsMet = false;
559             }
560             return requirementsMet;
561         }
562 
563         /**
564          * Returns the dialog message that should be displayed to the user based on the current
565          * state of the test.
566          */
getDialogMessage()567         private @StringRes int getDialogMessage() {
568             // Update the test state in case it was recently initialized to ensure the next
569             // required user action is displayed.
570             updateTestState(false);
571             switch (mTestState) {
572                 case STATE_AWAITING_LOCK_SCREEN_CONFIG:
573                     if (mBiometricsSupported) {
574                         return R.string.unlock_req_config_lock_screen_and_biometrics;
575                     }
576                     return R.string.unlock_req_config_lock_screen;
577                 case STATE_AWAITING_BIOMETRIC_CONFIG:
578                     return R.string.unlock_req_config_biometrics;
579                 case STATE_AWAITING_BIOMETRIC_LOCK:
580                     return R.string.unlock_req_biometric_lock;
581                 case STATE_BIOMETRIC_UNLOCK_COMPLETE:
582                 case STATE_CREDENTIAL_UNLOCK_COMPLETE:
583                     return R.string.unlock_req_unlocked;
584                 case STATE_AWAITING_CREDENTIAL_LOCK:
585                     return R.string.unlock_req_credential_lock;
586                 case STATE_TEST_SUCCESSFUL:
587                     return R.string.unlock_req_successful;
588                 case STATE_FAILED_KEY_AVAILABLE_IN_LOCK_STATE:
589                     return R.string.unlock_req_failed_key_available_when_locked;
590                 case STATE_FAILED_KEY_NOT_AVAILABLE_IN_UNLOCKED_STATE:
591                     return R.string.unlock_req_failed_key_unavailable_when_unlocked;
592                 // While all states are accounted for a default return is required; report an
593                 // unknown state if this is reached to ensure the test is retried from a clean
594                 // state.
595                 default:
596                     return R.string.unlock_req_unknown_state;
597             }
598         }
599     }
600 
601     /**
602      * {@link DialogFragment} used to display an AlertDialog to guide the user through the steps
603      * required for the test. A {@code DialogFragment} is used since it automatically handles
604      * configuration changes.
605      */
606     public static class TestDialogFragment extends DialogFragment {
607         private TestController mController;
608 
609         /**
610          * Creates a new {@link DialogFragment} that can obtain its text from the provided {@code
611          * controller}.
612          */
createDialogFragment(TestController controller)613         private static TestDialogFragment createDialogFragment(TestController controller) {
614             TestDialogFragment fragment = new TestDialogFragment();
615             fragment.mController = controller;
616             return fragment;
617         }
618 
619         @Override
onCreateDialog(Bundle savedInstanceState)620         public Dialog onCreateDialog(Bundle savedInstanceState) {
621             return new AlertDialog.Builder(getContext()).setMessage(
622                     mController.getDialogMessage()).create();
623         }
624     }
625 }
626