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