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