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