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