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