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