Support the ability to enroll face unlock first

Add a new EXTRA value to indicate whehter the face enrollment should be
launched first.

Bug: 370940762
Test: atest BiometricEnrollActivityTest
Flag: com.android.settings.flags.biometrics_onboarding_education
Change-Id: I7c85212c7fbcc6fe9dd53a26515412623c80ecbf
This commit is contained in:
Shawn Lin
2025-02-07 06:43:45 +00:00
parent 11b78c6fef
commit 52874a641a
3 changed files with 61 additions and 12 deletions

View File

@@ -107,7 +107,10 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
// intent will include this extra containing a bundle of the form:
// "modality" -> consented (boolean).
public static final String EXTRA_PARENTAL_CONSENT_STATUS = "consent_status";
// Whether the face enrollment should be launched first when there are multiple biometrics
// supported.
public static final String EXTRA_LAUNCH_FACE_ENROLL_FIRST =
"launch_face_enroll_first";
private static final String SAVED_STATE_CONFIRMING_CREDENTIALS = "confirming_credentials";
private static final String SAVED_STATE_IS_SINGLE_ENROLLING =
"is_single_enrolling";
@@ -130,6 +133,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
private boolean mIsFingerprintEnrollable = false;
private boolean mParentalOptionsRequired = false;
private boolean mSkipReturnToParent = false;
private boolean mLaunchFaceEnrollFirst = false;
private Bundle mParentalOptions;
@Nullable private Long mGkPwHandle;
@Nullable private ParentalConsentHelper mParentalConsentHelper;
@@ -214,6 +218,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
mParentalOptionsRequired = intent.getBooleanExtra(EXTRA_REQUIRE_PARENTAL_CONSENT, false);
mSkipReturnToParent = intent.getBooleanExtra(EXTRA_SKIP_RETURN_TO_PARENT, false);
mLaunchFaceEnrollFirst = intent.getBooleanExtra(EXTRA_LAUNCH_FACE_ENROLL_FIRST, false);
// determine what can be enrolled
final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
@@ -221,6 +226,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
Log.d(TAG, "parentalOptionsRequired: " + mParentalOptionsRequired
+ ", skipReturnToParent: " + mSkipReturnToParent
+ ", launchFaceEnrollFirst: " + mLaunchFaceEnrollFirst
+ ", isSetupWizard: " + isSetupWizard
+ ", isMultiSensor: " + isMultiSensor);
@@ -356,7 +362,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
} else if (canUseFace || canUseFingerprint) {
if (mGkPwHandle == null) {
setOrConfirmCredentialsNow();
} else if (canUseFingerprint && mIsFingerprintEnrollable) {
} else if (canUseFingerprint && mIsFingerprintEnrollable
&& !(canUseFace && mIsFaceEnrollable && mLaunchFaceEnrollFirst)) {
launchFingerprintOnlyEnroll();
} else if (canUseFace && mIsFaceEnrollable) {
launchFaceOnlyEnroll();
@@ -510,7 +517,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
int requestCode, int resultCode, Intent data) {
Log.d(TAG, "handleOnActivityResultWhileEnrolling, request = " + requestCode + ""
+ ", resultCode = " + resultCode);
+ ", resultCode = " + resultCode + ", launchFaceEnrollFirst="
+ mLaunchFaceEnrollFirst);
switch (requestCode) {
case REQUEST_HANDOFF_PARENT:
setResult(RESULT_OK, newResultIntent());
@@ -526,7 +534,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
// SetupFingerprintEnrollIntroduction/FingerprintEnrollmentActivity
TransitionHelper.applyForwardTransition(this, TRANSITION_FADE_THROUGH);
updateGatekeeperPasswordHandle(data);
if (mIsFingerprintEnrollable) {
if (mIsFingerprintEnrollable
&& !(mIsFaceEnrollable && mLaunchFaceEnrollFirst)) {
launchFingerprintOnlyEnroll();
} else {
launchFaceOnlyEnroll();
@@ -548,7 +557,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
}
if ((resultCode == BiometricEnrollBase.RESULT_SKIP
|| resultCode == BiometricEnrollBase.RESULT_FINISHED)
&& mIsFaceEnrollable) {
&& mIsFaceEnrollable && !mLaunchFaceEnrollFirst) {
// Apply forward animation during the transition from
// SetupFingerprintEnroll*/FingerprintEnrollmentActivity to
// SetupFaceEnrollIntroduction
@@ -556,6 +565,9 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
mIsPreviousEnrollmentCanceled =
resultCode != BiometricEnrollBase.RESULT_FINISHED;
launchFaceOnlyEnroll();
} else if (resultCode == Activity.RESULT_CANCELED && mIsFaceEnrollable
&& mLaunchFaceEnrollFirst) {
launchFaceOnlyEnroll();
} else {
notifySafetyIssueActionLaunchedIfNeeded(resultCode);
finishOrLaunchHandToParent(resultCode);
@@ -563,7 +575,14 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
break;
case REQUEST_SINGLE_ENROLL_FACE:
mIsSingleEnrolling = false;
if (resultCode == Activity.RESULT_CANCELED && mIsFingerprintEnrollable) {
if ((resultCode == BiometricEnrollBase.RESULT_SKIP
|| resultCode == BiometricEnrollBase.RESULT_FINISHED)
&& mIsFingerprintEnrollable && mLaunchFaceEnrollFirst) {
mIsPreviousEnrollmentCanceled =
resultCode != BiometricEnrollBase.RESULT_FINISHED;
launchFingerprintOnlyEnroll();
} else if (resultCode == Activity.RESULT_CANCELED && mIsFingerprintEnrollable
&& !mLaunchFaceEnrollFirst) {
mIsPreviousEnrollmentCanceled = true;
launchFingerprintOnlyEnroll();
} else {

View File

@@ -43,6 +43,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.biometrics.face.FaceEnroll;
import com.android.settings.biometrics.fingerprint.FingerprintEnroll;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor;
@@ -282,9 +283,7 @@ public class BiometricUtils {
*/
public static Intent getFaceIntroIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
final Intent intent = new Intent(context,
FeatureFactory.getFeatureFactory().getFaceFeatureProvider()
.getEnrollActivityClassProvider().getNext());
final Intent intent = new Intent(context, FaceEnroll.class);
WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
return intent;
}

View File

@@ -23,6 +23,7 @@ import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra;
import static com.android.settings.biometrics.BiometricEnrollActivity.EXTRA_LAUNCH_FACE_ENROLL_FIRST;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT;
@@ -39,6 +40,7 @@ import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
@@ -145,7 +147,7 @@ public class BiometricEnrollActivityTest {
assumeTrue(mHasFace || mHasFingerprint);
setPin();
final Intent intent = getIntent(true /* useInternal */);
final Intent intent = getIntent(true /* useInternal */, null);
LockPatternChecker.verifyCredential(new LockPatternUtils(mContext),
LockscreenCredential.createPin(TEST_PIN), UserHandle.myUserId(),
LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE, (response, timeoutMs) -> {
@@ -162,6 +164,26 @@ public class BiometricEnrollActivityTest {
}
}
@Test
public void launchWithPinAndPwHandle_confirmsPin_firstEnrollmentIsFace() throws Exception {
assumeTrue(mHasFace && mHasFingerprint);
setPin();
final Intent intent = getFaceEnrollFirstIntent();
LockPatternChecker.verifyCredential(new LockPatternUtils(mContext),
LockscreenCredential.createPin(TEST_PIN), UserHandle.myUserId(),
LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE, (response, timeoutMs) -> {
assertThat(response.containsGatekeeperPasswordHandle()).isTrue();
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
response.getGatekeeperPasswordHandle());
}).get();
try (ActivityScenario<BiometricEnrollActivity> scenario =
ActivityScenario.launch(intent)) {
intended(hasComponent(FaceEnroll.class.getName()));
}
}
@Test
public void launchWithStrongBiometricAllowed_doNotEnrollWeak() throws Exception {
assumeTrue(mHasFace || mHasFingerprint);
@@ -184,13 +206,22 @@ public class BiometricEnrollActivityTest {
}
private Intent getIntent() {
return getIntent(false /* useInternal */);
return getIntent(false /* useInternal */, null);
}
private Intent getIntent(boolean useInternal) {
private Intent getFaceEnrollFirstIntent() {
final Bundle bundle = new Bundle();
bundle.putBoolean(EXTRA_LAUNCH_FACE_ENROLL_FIRST, true);
return getIntent(true /* useInternal */, bundle);
}
private Intent getIntent(boolean useInternal, Bundle bundle) {
final Intent intent = new Intent(mContext, useInternal
? BiometricEnrollActivity.InternalActivity.class : BiometricEnrollActivity.class);
intent.setAction(ACTION_BIOMETRIC_ENROLL);
if (bundle != null && !bundle.isEmpty()) {
intent.putExtras(bundle);
}
return intent;
}