1 /* 2 * Copyright (C) 2018 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.settings.biometrics; 18 19 import static android.provider.Settings.ACTION_BIOMETRIC_ENROLL; 20 import static android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED; 21 22 import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_DENIED; 23 import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_GRANTED; 24 25 import static com.google.android.setupdesign.transition.TransitionHelper.TRANSITION_FADE_THROUGH; 26 27 import android.annotation.NonNull; 28 import android.app.Activity; 29 import android.app.admin.DevicePolicyManager; 30 import android.app.settings.SettingsEnums; 31 import android.content.Intent; 32 import android.content.pm.PackageManager; 33 import android.content.res.Resources; 34 import android.hardware.biometrics.BiometricAuthenticator; 35 import android.hardware.biometrics.BiometricManager; 36 import android.hardware.biometrics.BiometricManager.Authenticators; 37 import android.hardware.biometrics.BiometricManager.BiometricError; 38 import android.hardware.biometrics.SensorProperties; 39 import android.hardware.face.FaceManager; 40 import android.hardware.face.FaceSensorPropertiesInternal; 41 import android.hardware.fingerprint.FingerprintManager; 42 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; 43 import android.os.Bundle; 44 import android.os.UserHandle; 45 import android.os.UserManager; 46 import android.util.Log; 47 48 import androidx.annotation.Nullable; 49 50 import com.android.internal.util.FrameworkStatsLog; 51 import com.android.internal.widget.LockPatternUtils; 52 import com.android.settings.R; 53 import com.android.settings.SetupWizardUtils; 54 import com.android.settings.core.InstrumentedActivity; 55 import com.android.settings.overlay.FeatureFactory; 56 import com.android.settings.password.ChooseLockGeneric; 57 import com.android.settings.password.ChooseLockPattern; 58 import com.android.settings.password.ChooseLockSettingsHelper; 59 60 import com.google.android.setupcompat.util.WizardManagerHelper; 61 import com.google.android.setupdesign.transition.TransitionHelper; 62 63 import java.util.List; 64 65 /** 66 * Trampoline activity launched by the {@code android.settings.BIOMETRIC_ENROLL} action which 67 * shows the user an appropriate enrollment flow depending on the device's biometric hardware. 68 * This activity must only allow enrollment of biometrics that can be used by 69 * {@link android.hardware.biometrics.BiometricPrompt}. 70 */ 71 public class BiometricEnrollActivity extends InstrumentedActivity { 72 73 private static final String TAG = "BiometricEnrollActivity"; 74 75 private static final int REQUEST_CHOOSE_LOCK = 1; 76 private static final int REQUEST_CONFIRM_LOCK = 2; 77 // prompt for parental consent options 78 private static final int REQUEST_CHOOSE_OPTIONS = 3; 79 // prompt hand phone back to parent after enrollment 80 private static final int REQUEST_HANDOFF_PARENT = 4; 81 private static final int REQUEST_SINGLE_ENROLL_FINGERPRINT = 5; 82 private static final int REQUEST_SINGLE_ENROLL_FACE = 6; 83 84 public static final int RESULT_SKIP = BiometricEnrollBase.RESULT_SKIP; 85 86 // Intent extra. If true, biometric enrollment should skip introductory screens. Currently 87 // this only applies to fingerprint. 88 public static final String EXTRA_SKIP_INTRO = "skip_intro"; 89 90 // Intent extra. If true, parental consent will be requested before user enrollment. 91 public static final String EXTRA_REQUIRE_PARENTAL_CONSENT = "require_consent"; 92 93 // Intent extra. If true, the screen asking the user to return the device to their parent will 94 // be skipped after enrollment. 95 public static final String EXTRA_SKIP_RETURN_TO_PARENT = "skip_return_to_parent"; 96 97 // If EXTRA_REQUIRE_PARENTAL_CONSENT was used to start the activity then the result 98 // intent will include this extra containing a bundle of the form: 99 // "modality" -> consented (boolean). 100 public static final String EXTRA_PARENTAL_CONSENT_STATUS = "consent_status"; 101 102 private static final String SAVED_STATE_CONFIRMING_CREDENTIALS = "confirming_credentials"; 103 private static final String SAVED_STATE_IS_SINGLE_ENROLLING = 104 "is_single_enrolling"; 105 private static final String SAVED_STATE_PASS_THROUGH_EXTRAS_FROM_CHOSEN_LOCK_IN_SUW = 106 "pass_through_extras_from_chosen_lock_in_suw"; 107 private static final String SAVED_STATE_ENROLL_ACTION_LOGGED = "enroll_action_logged"; 108 private static final String SAVED_STATE_PARENTAL_OPTIONS = "enroll_preferences"; 109 private static final String SAVED_STATE_GK_PW_HANDLE = "gk_pw_handle"; 110 111 public static final class InternalActivity extends BiometricEnrollActivity {} 112 113 private int mUserId = UserHandle.myUserId(); 114 private boolean mConfirmingCredentials; 115 private boolean mIsSingleEnrolling; 116 private Bundle mPassThroughExtrasFromChosenLockInSuw = null; 117 private boolean mIsEnrollActionLogged; 118 private boolean mHasFeatureFace = false; 119 private boolean mHasFeatureFingerprint = false; 120 private boolean mIsFaceEnrollable = false; 121 private boolean mIsFingerprintEnrollable = false; 122 private boolean mParentalOptionsRequired = false; 123 private boolean mSkipReturnToParent = false; 124 private Bundle mParentalOptions; 125 @Nullable private Long mGkPwHandle; 126 @Nullable private ParentalConsentHelper mParentalConsentHelper; 127 128 @Override onCreate(@ullable Bundle savedInstanceState)129 public void onCreate(@Nullable Bundle savedInstanceState) { 130 super.onCreate(savedInstanceState); 131 132 final Intent intent = getIntent(); 133 134 if (this instanceof InternalActivity) { 135 mUserId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); 136 if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { 137 mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(getIntent()); 138 } 139 } else if (WizardManagerHelper.isAnySetupWizard(getIntent())) { 140 if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { 141 mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(getIntent()); 142 } 143 144 } 145 146 if (savedInstanceState != null) { 147 mConfirmingCredentials = savedInstanceState.getBoolean( 148 SAVED_STATE_CONFIRMING_CREDENTIALS, false); 149 mIsSingleEnrolling = savedInstanceState.getBoolean( 150 SAVED_STATE_IS_SINGLE_ENROLLING, false); 151 mPassThroughExtrasFromChosenLockInSuw = savedInstanceState.getBundle( 152 SAVED_STATE_PASS_THROUGH_EXTRAS_FROM_CHOSEN_LOCK_IN_SUW); 153 mIsEnrollActionLogged = savedInstanceState.getBoolean( 154 SAVED_STATE_ENROLL_ACTION_LOGGED, false); 155 mParentalOptions = savedInstanceState.getBundle(SAVED_STATE_PARENTAL_OPTIONS); 156 if (savedInstanceState.containsKey(SAVED_STATE_GK_PW_HANDLE)) { 157 mGkPwHandle = savedInstanceState.getLong(SAVED_STATE_GK_PW_HANDLE); 158 } 159 } 160 161 // Log a framework stats event if this activity was launched via intent action. 162 if (!mIsEnrollActionLogged && ACTION_BIOMETRIC_ENROLL.equals(intent.getAction())) { 163 mIsEnrollActionLogged = true; 164 165 // Get the current status for each authenticator type. 166 @BiometricError final int strongBiometricStatus; 167 @BiometricError final int weakBiometricStatus; 168 @BiometricError final int deviceCredentialStatus; 169 final BiometricManager bm = getSystemService(BiometricManager.class); 170 if (bm != null) { 171 strongBiometricStatus = bm.canAuthenticate(Authenticators.BIOMETRIC_STRONG); 172 weakBiometricStatus = bm.canAuthenticate(Authenticators.BIOMETRIC_WEAK); 173 deviceCredentialStatus = bm.canAuthenticate(Authenticators.DEVICE_CREDENTIAL); 174 } else { 175 strongBiometricStatus = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; 176 weakBiometricStatus = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; 177 deviceCredentialStatus = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; 178 } 179 180 FrameworkStatsLog.write(FrameworkStatsLog.AUTH_ENROLL_ACTION_INVOKED, 181 strongBiometricStatus == BiometricManager.BIOMETRIC_SUCCESS, 182 weakBiometricStatus == BiometricManager.BIOMETRIC_SUCCESS, 183 deviceCredentialStatus == BiometricManager.BIOMETRIC_SUCCESS, 184 intent.hasExtra(EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED), 185 intent.getIntExtra(EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, 0)); 186 } 187 188 // Put the theme in the intent so it gets propagated to other activities in the flow 189 if (intent.getStringExtra(WizardManagerHelper.EXTRA_THEME) == null) { 190 intent.putExtra( 191 WizardManagerHelper.EXTRA_THEME, 192 SetupWizardUtils.getThemeString(intent)); 193 } 194 195 final PackageManager pm = getApplicationContext().getPackageManager(); 196 mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT); 197 mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE); 198 199 // Default behavior is to enroll BIOMETRIC_WEAK or above. See ACTION_BIOMETRIC_ENROLL. 200 final int authenticators = getIntent().getIntExtra( 201 EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK); 202 Log.d(TAG, "Authenticators: " + BiometricManager.authenticatorToStr(authenticators)); 203 204 mParentalOptionsRequired = intent.getBooleanExtra(EXTRA_REQUIRE_PARENTAL_CONSENT, false); 205 mSkipReturnToParent = intent.getBooleanExtra(EXTRA_SKIP_RETURN_TO_PARENT, false); 206 207 // determine what can be enrolled 208 final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); 209 final boolean isMultiSensor = mHasFeatureFace && mHasFeatureFingerprint; 210 211 Log.d(TAG, "parentalOptionsRequired: " + mParentalOptionsRequired 212 + ", skipReturnToParent: " + mSkipReturnToParent 213 + ", isSetupWizard: " + isSetupWizard 214 + ", isMultiSensor: " + isMultiSensor); 215 216 if (mHasFeatureFace) { 217 final FaceManager faceManager = getSystemService(FaceManager.class); 218 final List<FaceSensorPropertiesInternal> faceProperties = 219 faceManager.getSensorPropertiesInternal(); 220 final int maxFacesEnrollableIfSUW = getApplicationContext().getResources() 221 .getInteger(R.integer.suw_max_faces_enrollable); 222 if (!faceProperties.isEmpty()) { 223 final FaceSensorPropertiesInternal props = faceProperties.get(0); 224 final int maxEnrolls = 225 isSetupWizard ? maxFacesEnrollableIfSUW : props.maxEnrollmentsPerUser; 226 final boolean isFaceStrong = 227 props.sensorStrength == SensorProperties.STRENGTH_STRONG; 228 mIsFaceEnrollable = 229 faceManager.getEnrolledFaces(mUserId).size() < maxEnrolls; 230 231 // If we expect strong bio only, check if face is strong 232 if (authenticators == Authenticators.BIOMETRIC_STRONG && !isFaceStrong) { 233 mIsFaceEnrollable = false; 234 } 235 236 final boolean parentalConsent = isSetupWizard || (mParentalOptionsRequired 237 && !WizardManagerHelper.isUserSetupComplete(this)); 238 if (parentalConsent && isMultiSensor && mIsFaceEnrollable) { 239 // Exclude face enrollment from setup wizard if feature config not supported 240 // in setup wizard flow, we still allow user enroll faces through settings. 241 mIsFaceEnrollable = FeatureFactory.getFactory(getApplicationContext()) 242 .getFaceFeatureProvider() 243 .isSetupWizardSupported(getApplicationContext()); 244 Log.d(TAG, "config_suw_support_face_enroll: " + mIsFaceEnrollable); 245 } 246 } 247 } 248 updateFingerprintEnrollable(isSetupWizard); 249 250 // TODO(b/195128094): remove this restriction 251 // Consent can only be recorded when this activity is launched directly from the kids 252 // module. This can be removed when there is a way to notify consent status out of band. 253 if (isSetupWizard && mParentalOptionsRequired) { 254 Log.w(TAG, "Enrollment with parental consent is not supported when launched " 255 + " directly from SuW - skipping enrollment"); 256 setResult(RESULT_SKIP); 257 finish(); 258 return; 259 } 260 261 // Only allow the consent flow to happen once when running from setup wizard. 262 // This isn't common and should only happen if setup wizard is not completed normally 263 // due to a restart, etc. 264 // This check should probably remain even if b/195128094 is fixed to prevent SuW from 265 // restarting the process once it has been fully completed at least one time. 266 if (isSetupWizard && mParentalOptionsRequired) { 267 final boolean consentAlreadyManaged = ParentalControlsUtils.parentConsentRequired(this, 268 BiometricAuthenticator.TYPE_FACE | BiometricAuthenticator.TYPE_FINGERPRINT) 269 != null; 270 if (consentAlreadyManaged) { 271 Log.w(TAG, "Consent was already setup - skipping enrollment"); 272 setResult(RESULT_SKIP); 273 finish(); 274 return; 275 } 276 } 277 278 if (mParentalOptionsRequired && mParentalOptions == null) { 279 mParentalConsentHelper = new ParentalConsentHelper(mGkPwHandle); 280 setOrConfirmCredentialsNow(); 281 } else { 282 // Start enrollment process if we haven't bailed out yet 283 startEnrollWith(authenticators, isSetupWizard); 284 } 285 } 286 287 private void updateFingerprintEnrollable(boolean isSetupWizard) { 288 if (mHasFeatureFingerprint) { 289 final int authenticators = getIntent().getIntExtra( 290 EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK); 291 292 final FingerprintManager fpManager = getSystemService(FingerprintManager.class); 293 final List<FingerprintSensorPropertiesInternal> fpProperties = 294 fpManager.getSensorPropertiesInternal(); 295 final int maxFingerprintsEnrollableIfSUW = getApplicationContext().getResources() 296 .getInteger(R.integer.suw_max_fingerprints_enrollable); 297 if (!fpProperties.isEmpty()) { 298 final int maxEnrolls = 299 isSetupWizard ? maxFingerprintsEnrollableIfSUW 300 : fpProperties.get(0).maxEnrollmentsPerUser; 301 final boolean isFingerprintStrong = 302 fpProperties.get(0).sensorStrength == SensorProperties.STRENGTH_STRONG; 303 mIsFingerprintEnrollable = 304 fpManager.getEnrolledFingerprints(mUserId).size() < maxEnrolls; 305 306 // If we expect strong bio only, check if fingerprint is strong 307 if (authenticators == Authenticators.BIOMETRIC_STRONG && !isFingerprintStrong) { 308 mIsFingerprintEnrollable = false; 309 } 310 } 311 } 312 } 313 314 private void startEnrollWith(@Authenticators.Types int authenticators, boolean setupWizard) { 315 // If the caller is not setup wizard, and the user has something enrolled, finish. 316 // Allow parental consent flow to skip this check, since one modality could be consented 317 // and another non-consented. This can also happen if the restriction is applied when 318 // enrollments already exists. 319 if (!setupWizard && !mParentalOptionsRequired) { 320 final BiometricManager bm = getSystemService(BiometricManager.class); 321 final @BiometricError int result = bm.canAuthenticate(authenticators); 322 if (result != BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) { 323 Log.e(TAG, "Unexpected result (has enrollments): " + result); 324 finish(); 325 return; 326 } 327 } 328 329 boolean canUseFace = mHasFeatureFace; 330 boolean canUseFingerprint = mHasFeatureFingerprint; 331 if (mParentalOptionsRequired) { 332 if (mParentalOptions == null) { 333 throw new IllegalStateException("consent options required, but not set"); 334 } 335 canUseFace = canUseFace 336 && ParentalConsentHelper.hasFaceConsent(mParentalOptions); 337 canUseFingerprint = canUseFingerprint 338 && ParentalConsentHelper.hasFingerprintConsent(mParentalOptions); 339 } 340 341 // This will need to be updated if the device has sensors other than BIOMETRIC_STRONG 342 if (!setupWizard && authenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) { 343 launchCredentialOnlyEnroll(); 344 finish(); 345 } else if (canUseFace || canUseFingerprint) { 346 if (mGkPwHandle == null) { 347 setOrConfirmCredentialsNow(); 348 } else if (canUseFingerprint && mIsFingerprintEnrollable) { 349 launchFingerprintOnlyEnroll(); 350 } else if (canUseFace && mIsFaceEnrollable) { 351 launchFaceOnlyEnroll(); 352 } else { 353 setOrConfirmCredentialsNow(); 354 } 355 } else { // no modalities available 356 if (mParentalOptionsRequired) { 357 Log.d(TAG, "No consent for any modality: skipping enrollment"); 358 setResult(RESULT_OK, newResultIntent()); 359 } else { 360 Log.e(TAG, "Unknown state, finishing (was SUW: " + setupWizard + ")"); 361 } 362 finish(); 363 } 364 } 365 366 @Override 367 protected void onSaveInstanceState(@NonNull Bundle outState) { 368 super.onSaveInstanceState(outState); 369 outState.putBoolean(SAVED_STATE_CONFIRMING_CREDENTIALS, mConfirmingCredentials); 370 outState.putBoolean(SAVED_STATE_IS_SINGLE_ENROLLING, mIsSingleEnrolling); 371 outState.putBundle(SAVED_STATE_PASS_THROUGH_EXTRAS_FROM_CHOSEN_LOCK_IN_SUW, 372 mPassThroughExtrasFromChosenLockInSuw); 373 outState.putBoolean(SAVED_STATE_ENROLL_ACTION_LOGGED, mIsEnrollActionLogged); 374 if (mParentalOptions != null) { 375 outState.putBundle(SAVED_STATE_PARENTAL_OPTIONS, mParentalOptions); 376 } 377 if (mGkPwHandle != null) { 378 outState.putLong(SAVED_STATE_GK_PW_HANDLE, mGkPwHandle); 379 } 380 } 381 382 @Override 383 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 384 super.onActivityResult(requestCode, resultCode, data); 385 386 if (isSuccessfulChooseCredential(requestCode, resultCode) 387 && data != null && data.getExtras() != null && data.getExtras().size() > 0 388 && WizardManagerHelper.isAnySetupWizard(getIntent())) { 389 mPassThroughExtrasFromChosenLockInSuw = data.getExtras(); 390 } 391 392 Log.d(TAG, 393 "onActivityResult(requestCode=" + requestCode + ", resultCode=" + resultCode + ")"); 394 // single enrollment is handled entirely by the launched activity 395 // this handles multi enroll or if parental consent is required 396 if (mParentalConsentHelper != null) { 397 // Lazily retrieve the values from ParentalControlUtils, since the value may not be 398 // ready in onCreate. 399 final boolean faceConsentRequired = ParentalControlsUtils 400 .parentConsentRequired(this, BiometricAuthenticator.TYPE_FACE) != null; 401 final boolean fpConsentRequired = ParentalControlsUtils 402 .parentConsentRequired(this, BiometricAuthenticator.TYPE_FINGERPRINT) != null; 403 404 final boolean requestFaceConsent = faceConsentRequired 405 && mHasFeatureFace 406 && mIsFaceEnrollable; 407 final boolean requestFpConsent = fpConsentRequired && mHasFeatureFingerprint; 408 409 Log.d(TAG, "faceConsentRequired: " + faceConsentRequired 410 + ", fpConsentRequired: " + fpConsentRequired 411 + ", hasFeatureFace: " + mHasFeatureFace 412 + ", hasFeatureFingerprint: " + mHasFeatureFingerprint 413 + ", faceEnrollable: " + mIsFaceEnrollable 414 + ", fpEnrollable: " + mIsFingerprintEnrollable); 415 416 mParentalConsentHelper.setConsentRequirement(requestFaceConsent, requestFpConsent); 417 418 handleOnActivityResultWhileConsenting(requestCode, resultCode, data); 419 } else { 420 handleOnActivityResultWhileEnrolling(requestCode, resultCode, data); 421 } 422 } 423 424 // handles responses while parental consent is pending 425 private void handleOnActivityResultWhileConsenting( 426 int requestCode, int resultCode, Intent data) { 427 overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); 428 429 switch (requestCode) { 430 case REQUEST_CHOOSE_LOCK: 431 case REQUEST_CONFIRM_LOCK: 432 mConfirmingCredentials = false; 433 if (isSuccessfulConfirmOrChooseCredential(requestCode, resultCode)) { 434 updateGatekeeperPasswordHandle(data); 435 if (!mParentalConsentHelper.launchNext(this, REQUEST_CHOOSE_OPTIONS)) { 436 Log.e(TAG, "Nothing to prompt for consent (no modalities enabled)!"); 437 finish(); 438 } 439 } else { 440 Log.d(TAG, "Unknown result for set/choose lock: " + resultCode); 441 setResult(resultCode); 442 finish(); 443 } 444 break; 445 case REQUEST_CHOOSE_OPTIONS: 446 if (resultCode == RESULT_CONSENT_GRANTED || resultCode == RESULT_CONSENT_DENIED) { 447 final boolean isStillPrompting = mParentalConsentHelper.launchNext( 448 this, REQUEST_CHOOSE_OPTIONS, resultCode, data); 449 if (!isStillPrompting) { 450 mParentalOptions = mParentalConsentHelper.getConsentResult(); 451 mParentalConsentHelper = null; 452 Log.d(TAG, "Enrollment consent options set, starting enrollment: " 453 + mParentalOptions); 454 // Note that we start enrollment with CONVENIENCE instead of the default 455 // of WEAK in startEnroll(), since we want to allow enrollment for any 456 // sensor as long as it has been consented for. We should eventually 457 // clean up this logic and do something like pass in the parental consent 458 // result, so that we can request enrollment for specific sensors, but 459 // that's quite a large and risky change to the startEnrollWith() logic. 460 startEnrollWith(Authenticators.BIOMETRIC_CONVENIENCE, 461 WizardManagerHelper.isAnySetupWizard(getIntent())); 462 } 463 } else { 464 Log.d(TAG, "Unknown or cancelled parental consent"); 465 setResult(RESULT_CANCELED, newResultIntent()); 466 finish(); 467 } 468 break; 469 default: 470 Log.w(TAG, "Unknown consenting requestCode: " + requestCode + ", finishing"); 471 finish(); 472 } 473 } 474 475 // handles responses while multi biometric enrollment is pending 476 private void handleOnActivityResultWhileEnrolling( 477 int requestCode, int resultCode, Intent data) { 478 479 Log.d(TAG, "handleOnActivityResultWhileEnrolling, request = " + requestCode + "" 480 + ", resultCode = " + resultCode); 481 switch (requestCode) { 482 case REQUEST_HANDOFF_PARENT: 483 setResult(RESULT_OK, newResultIntent()); 484 finish(); 485 break; 486 case REQUEST_CHOOSE_LOCK: 487 case REQUEST_CONFIRM_LOCK: 488 mConfirmingCredentials = false; 489 final boolean isOk = 490 isSuccessfulConfirmOrChooseCredential(requestCode, resultCode); 491 if (isOk && (mIsFaceEnrollable || mIsFingerprintEnrollable)) { 492 // Apply forward animation during the transition from ChooseLock/ConfirmLock to 493 // SetupFingerprintEnrollIntroduction/FingerprintEnrollmentActivity 494 TransitionHelper.applyForwardTransition(this, TRANSITION_FADE_THROUGH); 495 updateGatekeeperPasswordHandle(data); 496 if (mIsFingerprintEnrollable) { 497 launchFingerprintOnlyEnroll(); 498 } else { 499 launchFaceOnlyEnroll(); 500 } 501 } else { 502 Log.d(TAG, "Unknown result for set/choose lock: " + resultCode); 503 setResult(resultCode, newResultIntent()); 504 finish(); 505 } 506 break; 507 case REQUEST_SINGLE_ENROLL_FINGERPRINT: 508 mIsSingleEnrolling = false; 509 if (resultCode == BiometricEnrollBase.RESULT_FINISHED) { 510 // FingerprintEnrollIntroduction's visibility is determined by 511 // mIsFingerprintEnrollable. Keep this value up-to-date after a successful 512 // enrollment. 513 updateFingerprintEnrollable(WizardManagerHelper.isAnySetupWizard(getIntent())); 514 } 515 if ((resultCode == BiometricEnrollBase.RESULT_SKIP 516 || resultCode == BiometricEnrollBase.RESULT_FINISHED) 517 && mIsFaceEnrollable) { 518 // Apply forward animation during the transition from 519 // SetupFingerprintEnroll*/FingerprintEnrollmentActivity to 520 // SetupFaceEnrollIntroduction 521 TransitionHelper.applyForwardTransition(this, TRANSITION_FADE_THROUGH); 522 launchFaceOnlyEnroll(); 523 } else { 524 finishOrLaunchHandToParent(resultCode); 525 } 526 break; 527 case REQUEST_SINGLE_ENROLL_FACE: 528 mIsSingleEnrolling = false; 529 if (resultCode == Activity.RESULT_CANCELED && mIsFingerprintEnrollable) { 530 launchFingerprintOnlyEnroll(); 531 } else { 532 finishOrLaunchHandToParent(resultCode); 533 } 534 break; 535 default: 536 Log.w(TAG, "Unknown enrolling requestCode: " + requestCode + ", finishing"); 537 finish(); 538 } 539 } 540 541 @Override 542 public void finish() { 543 if (mGkPwHandle != null) { 544 // When launched as InternalActivity, the mGkPwHandle was gotten from intent extra 545 // instead of requesting from the user. Do not remove the mGkPwHandle in service side 546 // for this case because the caller activity may still need it and will be responsible 547 // for removing it. 548 if (!(this instanceof InternalActivity)) { 549 BiometricUtils.removeGatekeeperPasswordHandle(this, mGkPwHandle); 550 } 551 } 552 super.finish(); 553 } 554 555 private void finishOrLaunchHandToParent(int resultCode) { 556 if (mParentalOptionsRequired) { 557 if (!mSkipReturnToParent) { 558 launchHandoffToParent(); 559 } else { 560 setResult(RESULT_OK, newResultIntent()); 561 finish(); 562 } 563 } else { 564 setResult(resultCode, newResultIntent()); 565 finish(); 566 } 567 } 568 569 @NonNull 570 private Intent newResultIntent() { 571 final Intent intent = new Intent(); 572 if (mParentalOptionsRequired && mParentalOptions != null) { 573 final Bundle consentStatus = mParentalOptions.deepCopy(); 574 intent.putExtra(EXTRA_PARENTAL_CONSENT_STATUS, consentStatus); 575 Log.v(TAG, "Result consent status: " + consentStatus); 576 } 577 if (mPassThroughExtrasFromChosenLockInSuw != null) { 578 intent.putExtras(mPassThroughExtrasFromChosenLockInSuw); 579 } 580 return intent; 581 } 582 583 private static boolean isSuccessfulConfirmOrChooseCredential(int requestCode, int resultCode) { 584 return isSuccessfulChooseCredential(requestCode, resultCode) 585 || isSuccessfulConfirmCredential(requestCode, resultCode); 586 } 587 588 private static boolean isSuccessfulChooseCredential(int requestCode, int resultCode) { 589 return requestCode == REQUEST_CHOOSE_LOCK 590 && resultCode == ChooseLockPattern.RESULT_FINISHED; 591 } 592 593 private static boolean isSuccessfulConfirmCredential(int requestCode, int resultCode) { 594 return requestCode == REQUEST_CONFIRM_LOCK && resultCode == RESULT_OK; 595 } 596 597 @Override 598 protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) { 599 final int newResid = SetupWizardUtils.getTheme(this, getIntent()); 600 theme.applyStyle(R.style.SetupWizardPartnerResource, true); 601 super.onApplyThemeResource(theme, newResid, first); 602 } 603 604 private void setOrConfirmCredentialsNow() { 605 if (!mConfirmingCredentials) { 606 mConfirmingCredentials = true; 607 if (!userHasPassword(mUserId)) { 608 launchChooseLock(); 609 } else { 610 launchConfirmLock(); 611 } 612 } 613 } 614 615 private void updateGatekeeperPasswordHandle(@NonNull Intent data) { 616 mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data); 617 if (mParentalConsentHelper != null) { 618 mParentalConsentHelper.updateGatekeeperHandle(data); 619 } 620 } 621 622 private boolean userHasPassword(int userId) { 623 final UserManager userManager = getSystemService(UserManager.class); 624 final int passwordQuality = new LockPatternUtils(this) 625 .getActivePasswordQuality(userManager.getCredentialOwnerProfile(userId)); 626 return passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 627 } 628 629 private void launchChooseLock() { 630 Log.d(TAG, "launchChooseLock"); 631 632 Intent intent = BiometricUtils.getChooseLockIntent(this, getIntent()); 633 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); 634 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); 635 if (mIsFingerprintEnrollable && mIsFaceEnrollable) { 636 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, true); 637 } else if (mIsFaceEnrollable) { 638 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, true); 639 } else if (mIsFingerprintEnrollable) { 640 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, true); 641 } 642 643 if (mUserId != UserHandle.USER_NULL) { 644 intent.putExtra(Intent.EXTRA_USER_ID, mUserId); 645 } 646 startActivityForResult(intent, REQUEST_CHOOSE_LOCK); 647 } 648 649 private void launchConfirmLock() { 650 Log.d(TAG, "launchConfirmLock"); 651 652 final ChooseLockSettingsHelper.Builder builder = new ChooseLockSettingsHelper.Builder(this); 653 builder.setRequestCode(REQUEST_CONFIRM_LOCK) 654 .setRequestGatekeeperPasswordHandle(true) 655 .setForegroundOnly(true) 656 .setReturnCredentials(true); 657 if (mUserId != UserHandle.USER_NULL) { 658 builder.setUserId(mUserId); 659 } 660 final boolean launched = builder.show(); 661 if (!launched) { 662 // This shouldn't happen, as we should only end up at this step if a lock thingy is 663 // already set. 664 finish(); 665 } 666 } 667 668 // This should only be used to launch enrollment for single-sensor devices. 669 private void launchSingleSensorEnrollActivity(@NonNull Intent intent, int requestCode) { 670 byte[] hardwareAuthToken = null; 671 if (this instanceof InternalActivity) { 672 hardwareAuthToken = getIntent().getByteArrayExtra( 673 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 674 } 675 BiometricUtils.launchEnrollForResult(this, intent, requestCode, hardwareAuthToken, 676 mGkPwHandle, mUserId); 677 } 678 679 private void launchCredentialOnlyEnroll() { 680 final Intent intent; 681 // If only device credential was specified, ask the user to only set that up. 682 intent = new Intent(this, ChooseLockGeneric.class); 683 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); 684 launchSingleSensorEnrollActivity(intent, 0 /* requestCode */); 685 } 686 687 private void launchFingerprintOnlyEnroll() { 688 if (!mIsSingleEnrolling) { 689 mIsSingleEnrolling = true; 690 final Intent intent; 691 // ChooseLockGeneric can request to start fingerprint enroll bypassing the intro screen. 692 if (getIntent().getBooleanExtra(EXTRA_SKIP_INTRO, false) 693 && this instanceof InternalActivity) { 694 intent = BiometricUtils.getFingerprintFindSensorIntent(this, getIntent()); 695 } else { 696 intent = BiometricUtils.getFingerprintIntroIntent(this, getIntent()); 697 } 698 launchSingleSensorEnrollActivity(intent, REQUEST_SINGLE_ENROLL_FINGERPRINT); 699 } 700 } 701 702 private void launchFaceOnlyEnroll() { 703 if (!mIsSingleEnrolling) { 704 mIsSingleEnrolling = true; 705 final Intent intent = BiometricUtils.getFaceIntroIntent(this, getIntent()); 706 launchSingleSensorEnrollActivity(intent, REQUEST_SINGLE_ENROLL_FACE); 707 } 708 } 709 710 private void launchHandoffToParent() { 711 final Intent intent = BiometricUtils.getHandoffToParentIntent(this, getIntent()); 712 startActivityForResult(intent, REQUEST_HANDOFF_PARENT); 713 } 714 715 @Override 716 public int getMetricsCategory() { 717 return SettingsEnums.BIOMETRIC_ENROLL_ACTIVITY; 718 } 719 } 720