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