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