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