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