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