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