3-1/ Impl FoldProvider.FoldCallback for Face enroll activities

Create a mechanism to allow OEM config posture guidance with
'config_face_enroll_guidance_page', and customize the config
'config_face_enroll_supported_posture' with standard postures
         0 : DEVICE_POSTURE_UNKNOWN
         1 : DEVICE_POSTURE_CLOSED
         2 : DEVICE_POSTURE_HALF_OPENED
         3 : DEVICE_POSTURE_OPENED
         4 : DEVICE_POSTURE_FLIPPED
For example, if we set 1 for the device, then device only
allow to enroll face in closed(folded) state, if device do
not in the allow state, we will prompt specific guidance
page activity defined in config_face_enroll_guidance_page.

At this stage , we only integrate 2 states OPENED/CLOSED through
ScreenSizeFoldProvider and register for onFoldUpdated() callback
- isFold(DEVICE_POSTURE_CLOSED): finish posture guidance
- !isFold(DEVICE_POSTURE_OPENED): launch posture guidance
- onActivityResult : reset mOnGuidanceShown false

1. Fix A11y lottie animation bug
2. Impl FoldProvider.FoldCallback
3. Register callback to ScreenSizeFoldProvider
4. Integrate back stack, skip, cancel events
   - Back key : RESULT_CANCELED
   - Skip btn : RESULT_SKIP
   - Posture changed : RESULT_FINISHED
5. Set single instance for relative activities
6. FaceEnrollFoldPage listen for onConfigurationChanged()
7. Add empty face_posture_guidance_lottie.json for overlay

Test: atest SettingsGoogleUnitTests
Test: m -j SettingsGoogleRoboTests RunSettingsGoogleRoboTests
Test: m RunSettingsRoboTests ROBOTEST_FILTER= \
      "com.android.settings.biometrics.face.FaceEnrollEducationTest"
Test: m RunSettingsRoboTests ROBOTEST_FILTER= \
      "com.android.settings.biometrics.face.FaceEnrollIntroductionTest"
Test: Manual launch security settings face enroll, unfold device
and observe posture guidance showing fullscreen on top
Test: Fold device ensure the posture guidance activity finish
Bug: 261141826
Fixes: 231908496

Change-Id: Ib9f43f82f7d19f3f187c2f6f8984e76cd843afbc
This commit is contained in:
lbill
2022-12-08 07:11:23 +00:00
parent 1eee5eda6c
commit ee6366761b
12 changed files with 859 additions and 103 deletions

View File

@@ -272,6 +272,19 @@
<!-- ComponentName to launch a vendor-specific enrollment activity if available -->
<string name="config_face_enroll" translatable="false"></string>
<!-- ComponentName to launch a vendor-specific posture guidance activity if available -->
<string name="config_face_enroll_guidance_page" translatable="false"></string>
<!-- Whether to support posture listening for face auth, default is 0(DEVICE_POSTURE_UNKNOWN)
means setting will try listening on device posture changes.
0 : DEVICE_POSTURE_UNKNOWN
1 : DEVICE_POSTURE_CLOSED
2 : DEVICE_POSTURE_HALF_OPENED
3 : DEVICE_POSTURE_OPENED
4 : DEVICE_POSTURE_FLIPPED
-->
<integer name="config_face_enroll_supported_posture">0</integer>
<!-- Whether to show the "less secure" info section on the face enroll intro screen -->
<bool name="config_face_intro_show_less_secure">false</bool>

View File

@@ -36,6 +36,7 @@ import androidx.window.embedding.SplitRule;
import com.android.settings.Settings;
import com.android.settings.SettingsActivity;
import com.android.settings.SubSettings;
import com.android.settings.biometrics.face.FaceEnrollIntroductionInternal;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal;
@@ -230,6 +231,8 @@ public class ActivityEmbeddingRulesController {
addActivityFilter(activityFilters, FingerprintEnrollIntroduction.class);
addActivityFilter(activityFilters, FingerprintEnrollIntroductionInternal.class);
addActivityFilter(activityFilters, FingerprintEnrollEnrolling.class);
addActivityFilter(activityFilters, FaceEnrollIntroductionInternal.class);
addActivityFilter(activityFilters, Settings.FaceSettingsInternalActivity.class);
addActivityFilter(activityFilters, AvatarPickerActivity.class);
mSplitController.registerRule(new ActivityRule(activityFilters, true /* alwaysExpand */));
}

View File

@@ -38,7 +38,10 @@ import com.android.settings.SetupWizardUtils;
import com.android.settings.Utils;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
import com.android.settings.core.InstrumentedActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
import com.android.systemui.unfold.updates.FoldProvider;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
@@ -60,8 +63,10 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
public static final String EXTRA_KEY_SENSOR_ID = "sensor_id";
public static final String EXTRA_KEY_CHALLENGE = "challenge";
public static final String EXTRA_KEY_MODALITY = "sensor_modality";
public static final String EXTRA_KEY_NEXT_LAUNCHED = "next_launched";
public static final String EXTRA_FINISHED_ENROLL_FACE = "finished_enrolling_face";
public static final String EXTRA_FINISHED_ENROLL_FINGERPRINT = "finished_enrolling_fingerprint";
public static final String EXTRA_LAUNCHED_POSTURE_GUIDANCE = "launched_posture_guidance";
/**
* Used by the choose fingerprint wizard to indicate the wizard is
@@ -115,14 +120,25 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
* example, when starting fingerprint enroll after face enroll.
*/
public static final int ENROLL_NEXT_BIOMETRIC_REQUEST = 6;
public static final int REQUEST_POSTURE_GUIDANCE = 7;
protected boolean mLaunchedConfirmLock;
protected boolean mLaunchedPostureGuidance;
protected boolean mNextLaunched;
protected byte[] mToken;
protected int mUserId;
protected int mSensorId;
@BiometricUtils.DevicePostureInt
protected int mDevicePostureState;
protected long mChallenge;
protected boolean mFromSettingsSummary;
protected FooterBarMixin mFooterBarMixin;
@Nullable
protected ScreenSizeFoldProvider mScreenSizeFoldProvider;
@Nullable
protected Intent mPostureGuidanceIntent = null;
@Nullable
protected FoldProvider.FoldCallback mFoldCallback = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -139,16 +155,23 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
}
mFromSettingsSummary = getIntent().getBooleanExtra(EXTRA_FROM_SETTINGS_SUMMARY, false);
if (savedInstanceState != null && mToken == null) {
mLaunchedConfirmLock = savedInstanceState.getBoolean(EXTRA_KEY_LAUNCHED_CONFIRM);
mToken = savedInstanceState.getByteArray(
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
mFromSettingsSummary =
savedInstanceState.getBoolean(EXTRA_FROM_SETTINGS_SUMMARY, false);
mChallenge = savedInstanceState.getLong(EXTRA_KEY_CHALLENGE);
mSensorId = savedInstanceState.getInt(EXTRA_KEY_SENSOR_ID);
if (savedInstanceState != null) {
if (mToken == null) {
mLaunchedConfirmLock = savedInstanceState.getBoolean(EXTRA_KEY_LAUNCHED_CONFIRM);
mToken = savedInstanceState.getByteArray(
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
mFromSettingsSummary =
savedInstanceState.getBoolean(EXTRA_FROM_SETTINGS_SUMMARY, false);
mChallenge = savedInstanceState.getLong(EXTRA_KEY_CHALLENGE);
mSensorId = savedInstanceState.getInt(EXTRA_KEY_SENSOR_ID);
}
mLaunchedPostureGuidance = savedInstanceState.getBoolean(
EXTRA_LAUNCHED_POSTURE_GUIDANCE);
mNextLaunched = savedInstanceState.getBoolean(EXTRA_KEY_NEXT_LAUNCHED);
}
mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
mPostureGuidanceIntent = FeatureFactory.getFactory(getApplicationContext())
.getFaceFeatureProvider().getPostureGuidanceIntent(getApplicationContext());
}
@Override
@@ -159,6 +182,8 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
outState.putBoolean(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary);
outState.putLong(EXTRA_KEY_CHALLENGE, mChallenge);
outState.putInt(EXTRA_KEY_SENSOR_ID, mSensorId);
outState.putBoolean(EXTRA_LAUNCHED_POSTURE_GUIDANCE, mLaunchedPostureGuidance);
outState.putBoolean(EXTRA_KEY_NEXT_LAUNCHED, mNextLaunched);
}
@Override
@@ -184,6 +209,12 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
@Override
protected void onStop() {
super.onStop();
if (mScreenSizeFoldProvider != null && mFoldCallback != null) {
mScreenSizeFoldProvider.unregisterCallback(mFoldCallback);
}
mScreenSizeFoldProvider = null;
mFoldCallback = null;
if (!isChangingConfigurations() && shouldFinishWhenBackgrounded()
&& !BiometricUtils.isAnyMultiBiometricFlow(this)) {
setResult(RESULT_TIMEOUT);
@@ -191,6 +222,17 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
}
}
protected boolean launchPostureGuidance() {
if (mPostureGuidanceIntent == null || mLaunchedPostureGuidance) {
return false;
}
BiometricUtils.copyMultiBiometricExtras(getIntent(), mPostureGuidanceIntent);
startActivityForResult(mPostureGuidanceIntent, REQUEST_POSTURE_GUIDANCE);
mLaunchedPostureGuidance = true;
overridePendingTransition(0 /* no enter anim */, 0 /* no exit anim */);
return mLaunchedPostureGuidance;
}
protected boolean shouldFinishWhenBackgrounded() {
return !WizardManagerHelper.isAnySetupWizard(getIntent());
}

View File

@@ -157,6 +157,8 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
if (savedInstanceState != null) {
mConfirmingCredentials = savedInstanceState.getBoolean(KEY_CONFIRMING_CREDENTIALS);
mHasScrolledToBottom = savedInstanceState.getBoolean(KEY_SCROLLED_TO_BOTTOM);
mLaunchedPostureGuidance = savedInstanceState.getBoolean(
EXTRA_LAUNCHED_POSTURE_GUIDANCE);
}
Intent intent = getIntent();
@@ -295,6 +297,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
finish();
}
}
mNextLaunched = true;
}
private void launchChooseLock() {

View File

@@ -16,6 +16,7 @@
package com.android.settings.biometrics;
import android.annotation.IntDef;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
@@ -49,12 +50,37 @@ import com.android.settings.password.SetupChooseLockGeneric;
import com.google.android.setupcompat.util.WizardManagerHelper;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Common biometric utilities.
*/
public class BiometricUtils {
private static final String TAG = "BiometricUtils";
// Note: Theis IntDef must align SystemUI DevicePostureInt
@IntDef(prefix = {"DEVICE_POSTURE_"}, value = {
DEVICE_POSTURE_UNKNOWN,
DEVICE_POSTURE_CLOSED,
DEVICE_POSTURE_HALF_OPENED,
DEVICE_POSTURE_OPENED,
DEVICE_POSTURE_FLIPPED
})
@Retention(RetentionPolicy.SOURCE)
public @interface DevicePostureInt {}
// NOTE: These constants **must** match those defined for Jetpack Sidecar. This is because we
// use the Device State -> Jetpack Posture map in DevicePostureControllerImpl to translate
// between the two.
public static final int DEVICE_POSTURE_UNKNOWN = 0;
public static final int DEVICE_POSTURE_CLOSED = 1;
public static final int DEVICE_POSTURE_HALF_OPENED = 2;
public static final int DEVICE_POSTURE_OPENED = 3;
public static final int DEVICE_POSTURE_FLIPPED = 4;
public static int sAllowEnrollPosture = DEVICE_POSTURE_UNKNOWN;
/**
* Request was sent for starting another enrollment of a previously
* enrolled biometric of the same type.
@@ -334,6 +360,51 @@ public class BiometricUtils {
|| isMultiBiometricFingerprintEnrollmentFlow(activity);
}
/**
* Used to check if the activity is showing a posture guidance to user.
*
* @param devicePosture the device posture state
* @param isLaunchedPostureGuidance True launching a posture guidance to user
* @return True if the activity is showing posture guidance to user
*/
public static boolean isPostureGuidanceShowing(@DevicePostureInt int devicePosture,
boolean isLaunchedPostureGuidance) {
return !isPostureAllowEnrollment(devicePosture) && isLaunchedPostureGuidance;
}
/**
* Used to check if current device posture state is allow to enroll biometrics.
* For compatibility, we don't restrict enrollment if device do not config.
*
* @param devicePosture True if current device posture allow enrollment
* @return True if current device posture state allow enrollment
*/
public static boolean isPostureAllowEnrollment(@DevicePostureInt int devicePosture) {
return (sAllowEnrollPosture == DEVICE_POSTURE_UNKNOWN)
|| (devicePosture == sAllowEnrollPosture);
}
/**
* Used to check if the activity should show a posture guidance to user.
*
* @param devicePosture the device posture state
* @param isLaunchedPostureGuidance True launching a posture guidance to user
* @return True if posture disallow enroll and posture guidance not showing, false otherwise.
*/
public static boolean shouldShowPostureGuidance(@DevicePostureInt int devicePosture,
boolean isLaunchedPostureGuidance) {
return !isPostureAllowEnrollment(devicePosture) && !isLaunchedPostureGuidance;
}
/**
* Sets allowed device posture for face enrollment.
*
* @param devicePosture the allowed posture state {@link DevicePostureInt} for enrollment
*/
public static void setDevicePosturesAllowEnroll(@DevicePostureInt int devicePosture) {
sAllowEnrollPosture = devicePosture;
}
public static void copyMultiBiometricExtras(@NonNull Intent fromIntent,
@NonNull Intent toIntent) {
PendingIntent pendingIntent = (PendingIntent) fromIntent.getExtra(

View File

@@ -16,24 +16,35 @@
package com.android.settings.biometrics.face;
import static com.android.settings.biometrics.BiometricUtils.isPostureAllowEnrollment;
import static com.android.settings.biometrics.BiometricUtils.isPostureGuidanceShowing;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.face.FaceManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.CompoundButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.SetupSkipDialog;
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
import com.android.systemui.unfold.updates.FoldProvider;
import com.airbnb.lottie.LottieAnimationView;
import com.google.android.setupcompat.template.FooterBarMixin;
@@ -41,18 +52,19 @@ import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.view.IllustrationVideoView;
/**
* Provides animated education for users to know how to enroll a face with appropriate posture.
*/
public class FaceEnrollEducation extends BiometricEnrollBase {
private static final String TAG = "FaceEducation";
private FaceManager mFaceManager;
private FaceEnrollAccessibilityToggle mSwitchDiversity;
private boolean mIsUsingLottie;
private IllustrationVideoView mIllustrationDefault;
private LottieAnimationView mIllustrationLottie;
private View mIllustrationAccessibility;
private Intent mResultIntent;
private boolean mNextClicked;
private boolean mAccessibilityEnabled;
private final CompoundButton.OnCheckedChangeListener mSwitchDiversityListener =
@@ -154,6 +166,34 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
}
}
@Override
protected void onStart() {
super.onStart();
if (getPostureGuidanceIntent() == null) {
Log.d(TAG, "Device do not support posture guidance");
return;
}
BiometricUtils.setDevicePosturesAllowEnroll(
getResources().getInteger(R.integer.config_face_enroll_supported_posture));
if (getPostureCallback() == null) {
mFoldCallback = isFolded -> {
mDevicePostureState = isFolded ? BiometricUtils.DEVICE_POSTURE_CLOSED
: BiometricUtils.DEVICE_POSTURE_OPENED;
if (BiometricUtils.shouldShowPostureGuidance(mDevicePostureState,
mLaunchedPostureGuidance) && !mNextLaunched) {
launchPostureGuidance();
}
};
}
if (mScreenSizeFoldProvider == null) {
mScreenSizeFoldProvider = new ScreenSizeFoldProvider(getApplicationContext());
mScreenSizeFoldProvider.registerCallback(mFoldCallback, getMainExecutor());
}
}
@Override
protected void onResume() {
super.onResume();
@@ -172,7 +212,8 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
@Override
protected boolean shouldFinishWhenBackgrounded() {
return super.shouldFinishWhenBackgrounded() && !mNextClicked;
return super.shouldFinishWhenBackgrounded() && !mNextLaunched
&& !isPostureGuidanceShowing(mDevicePostureState, mLaunchedPostureGuidance);
}
@Override
@@ -206,13 +247,14 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
FaceEnrollAccessibilityDialog dialog = FaceEnrollAccessibilityDialog.newInstance();
dialog.setPositiveButtonListener((dialog1, which) -> {
startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST);
mNextClicked = true;
mNextLaunched = true;
});
dialog.show(getSupportFragmentManager(), FaceEnrollAccessibilityDialog.class.getName());
} else {
startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST);
mNextClicked = true;
mNextLaunched = true;
}
}
protected void onSkipButtonClick(View view) {
@@ -223,15 +265,29 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
}
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mScreenSizeFoldProvider != null && getPostureCallback() != null) {
mScreenSizeFoldProvider.onConfigurationChange(newConfig);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_POSTURE_GUIDANCE) {
mLaunchedPostureGuidance = false;
if (resultCode == RESULT_CANCELED || resultCode == RESULT_SKIP) {
onSkipButtonClick(getCurrentFocus());
}
return;
}
mResultIntent = data;
boolean hasEnrolledFace = false;
if (data != null) {
hasEnrolledFace = data.getBooleanExtra(EXTRA_FINISHED_ENROLL_FACE, false);
}
if (resultCode == RESULT_TIMEOUT) {
if (resultCode == RESULT_TIMEOUT || !isPostureAllowEnrollment(mDevicePostureState)) {
setResult(resultCode, data);
finish();
} else if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST
@@ -243,6 +299,26 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
finish();
}
}
mNextLaunched = false;
super.onActivityResult(requestCode, resultCode, data);
}
@VisibleForTesting
@Nullable
protected Intent getPostureGuidanceIntent() {
return mPostureGuidanceIntent;
}
@VisibleForTesting
@Nullable
protected FoldProvider.FoldCallback getPostureCallback() {
return mFoldCallback;
}
@VisibleForTesting
@BiometricUtils.DevicePostureInt
protected int getDevicePostureState() {
return mDevicePostureState;
}
@Override
@@ -262,8 +338,10 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
private void showDefaultIllustration() {
if (mIsUsingLottie) {
mIllustrationLottie.setAnimation(R.raw.face_education_lottie);
mIllustrationLottie.setVisibility(View.VISIBLE);
mIllustrationLottie.playAnimation();
mIllustrationLottie.setProgress(0f);
} else {
mIllustrationDefault.setVisibility(View.VISIBLE);
mIllustrationDefault.start();

View File

@@ -23,6 +23,7 @@ import static com.android.settings.biometrics.BiometricUtils.GatekeeperCredentia
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.face.FaceManager;
@@ -50,6 +51,8 @@ import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.SetupSkipDialog;
import com.android.settings.utils.SensorPrivacyManagerHelper;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
import com.android.systemui.unfold.updates.FoldProvider;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -99,6 +102,12 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
}
}
@Override
protected boolean shouldFinishWhenBackgrounded() {
return super.shouldFinishWhenBackgrounded() && !BiometricUtils.isPostureGuidanceShowing(
mDevicePostureState, mLaunchedPostureGuidance);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -188,14 +197,76 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
return Utils.getFaceManagerOrNull(this);
}
@VisibleForTesting
@Nullable
protected Intent getPostureGuidanceIntent() {
return mPostureGuidanceIntent;
}
@VisibleForTesting
@Nullable
protected FoldProvider.FoldCallback getPostureCallback() {
return mFoldCallback;
}
@VisibleForTesting
@BiometricUtils.DevicePostureInt
protected int getDevicePostureState() {
return mDevicePostureState;
}
@VisibleForTesting
@Nullable
protected byte[] requestGatekeeperHat(long challenge) {
return BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mScreenSizeFoldProvider != null && getPostureCallback() != null) {
mScreenSizeFoldProvider.onConfigurationChange(newConfig);
}
}
@Override
protected void onStart() {
super.onStart();
if (getPostureGuidanceIntent() == null) {
Log.d(TAG, "Device do not support posture guidance");
return;
}
BiometricUtils.setDevicePosturesAllowEnroll(
getResources().getInteger(R.integer.config_face_enroll_supported_posture));
if (getPostureCallback() == null) {
mFoldCallback = isFolded -> {
mDevicePostureState = isFolded ? BiometricUtils.DEVICE_POSTURE_CLOSED
: BiometricUtils.DEVICE_POSTURE_OPENED;
if (BiometricUtils.shouldShowPostureGuidance(mDevicePostureState,
mLaunchedPostureGuidance) && !mNextLaunched) {
launchPostureGuidance();
}
};
}
if (mScreenSizeFoldProvider == null) {
mScreenSizeFoldProvider = new ScreenSizeFoldProvider(getApplicationContext());
mScreenSizeFoldProvider.registerCallback(mFoldCallback, getMainExecutor());
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_POSTURE_GUIDANCE) {
mLaunchedPostureGuidance = false;
if (resultCode == RESULT_CANCELED || resultCode == RESULT_SKIP) {
onSkipButtonClick(getCurrentFocus());
}
return;
}
// If user has skipped or finished enrolling, don't restart enrollment.
final boolean isEnrollRequest = requestCode == BIOMETRIC_FIND_SENSOR_REQUEST
|| requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST;
@@ -206,10 +277,12 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
hasEnrolledFace = data.getBooleanExtra(EXTRA_FINISHED_ENROLL_FACE, false);
}
if (resultCode == RESULT_CANCELED && hasEnrolledFace) {
setResult(resultCode, data);
finish();
return;
if (resultCode == RESULT_CANCELED) {
if (hasEnrolledFace || !BiometricUtils.isPostureAllowEnrollment(mDevicePostureState)) {
setResult(resultCode, data);
finish();
return;
}
}
if (isEnrollRequest && isResultSkipOrFinished || hasEnrolledFace) {

View File

@@ -17,9 +17,16 @@
package com.android.settings.biometrics.face;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.Nullable;
/** Feature provider for face unlock */
public interface FaceFeatureProvider {
/** Returns specified intent config by resource R.string.config_face_enroll_guidance_page. */
@Nullable
Intent getPostureGuidanceIntent(Context context);
/** Returns true if attention checking is supported. */
boolean isAttentionSupported(Context context);

View File

@@ -16,12 +16,41 @@
package com.android.settings.biometrics.face;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
public class FaceFeatureProviderImpl implements FaceFeatureProvider {
/**
* Returns the guidance page intent if device support {@link FoldingFeature}, and we want to
* guide user enrolling faces with specific device posture.
*
* @param context the application context
* @return the posture guidance intent, otherwise null if device not support
*/
@Nullable
@Override
public Intent getPostureGuidanceIntent(Context context) {
final String flattenedString = context.getString(R.string.config_face_enroll_guidance_page);
final Intent intent;
if (!TextUtils.isEmpty(flattenedString)) {
ComponentName componentName = ComponentName.unflattenFromString(flattenedString);
if (componentName != null) {
intent = new Intent();
intent.setComponent(componentName);
return intent;
}
}
return null;
}
@Override
public boolean isAttentionSupported(Context context) {
return true;

View File

@@ -0,0 +1,225 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics.face;
import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static android.util.DisplayMetrics.DENSITY_XXXHIGH;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_NEXT_LAUNCHED;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_LAUNCHED_POSTURE_GUIDANCE;
import static com.android.settings.biometrics.BiometricUtils.DEVICE_POSTURE_CLOSED;
import static com.android.settings.biometrics.BiometricUtils.DEVICE_POSTURE_OPENED;
import static com.android.settings.biometrics.BiometricUtils.DEVICE_POSTURE_UNKNOWN;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.face.FaceManager;
import android.view.View;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowUtils.class})
public class FaceEnrollEducationTest {
@Mock
private FaceManager mFaceManager;
private Context mContext;
private ActivityController<TestFaceEnrollEducation> mActivityController;
private TestFaceEnrollEducation mActivity;
private FakeFeatureFactory mFakeFeatureFactory;
public static class TestFaceEnrollEducation extends FaceEnrollEducation {
@Override
protected boolean launchPostureGuidance() {
return super.launchPostureGuidance();
}
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ShadowUtils.setFaceManager(mFaceManager);
mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
}
@After
public void tearDown() {
ShadowUtils.reset();
}
private void setupActivityForPosture() {
final Intent testIntent = new Intent();
// Set the challenge token so the confirm screen will not be shown
testIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]);
testIntent.putExtra(EXTRA_KEY_NEXT_LAUNCHED, false);
testIntent.putExtra(EXTRA_LAUNCHED_POSTURE_GUIDANCE, false);
when(mFakeFeatureFactory.mFaceFeatureProvider.getPostureGuidanceIntent(any())).thenReturn(
testIntent);
mContext = spy(ApplicationProvider.getApplicationContext());
mActivityController = Robolectric.buildActivity(
TestFaceEnrollEducation.class, testIntent);
mActivity = spy(mActivityController.create().get());
when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
}
private void setupActivity() {
final Intent testIntent = new Intent();
// Set the challenge token so the confirm screen will not be shown
testIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]);
when(mFakeFeatureFactory.mFaceFeatureProvider.getPostureGuidanceIntent(any())).thenReturn(
null /* Simulate no posture intent */);
mContext = spy(ApplicationProvider.getApplicationContext());
mActivityController = Robolectric.buildActivity(
TestFaceEnrollEducation.class, testIntent);
mActivity = spy(mActivityController.create().get());
when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
}
private GlifLayout getGlifLayout() {
return mActivity.findViewById(R.id.setup_wizard_layout);
}
@Test
public void testFaceEnrollEducation_hasHeader() {
setupActivity();
CharSequence headerText = getGlifLayout().getHeaderText();
assertThat(headerText.toString()).isEqualTo(
mContext.getString(R.string.security_settings_face_enroll_education_title));
}
@Test
public void testFaceEnrollEducation_hasDescription() {
setupActivity();
CharSequence desc = getGlifLayout().getDescriptionText();
assertThat(desc.toString()).isEqualTo(
mContext.getString(R.string.security_settings_face_enroll_education_message));
}
@Test
public void testFaceEnrollEducation_showFooterPrimaryButton() {
setupActivity();
FooterBarMixin footer = getGlifLayout().getMixin(FooterBarMixin.class);
FooterButton footerButton = footer.getPrimaryButton();
assertThat(footerButton.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(footerButton.getText().toString()).isEqualTo(
mContext.getString(R.string.security_settings_face_enroll_education_start));
}
@Test
public void testFaceEnrollEducation_showFooterSecondaryButton() {
setupActivity();
FooterBarMixin footer = getGlifLayout().getMixin(FooterBarMixin.class);
FooterButton footerButton = footer.getSecondaryButton();
assertThat(footerButton.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(footerButton.getText().toString()).isEqualTo(mContext.getString(
R.string.security_settings_face_enroll_introduction_cancel));
}
@Test
public void testFaceEnrollEducation_defaultNeverLaunchPostureGuidance() {
setupActivity();
assertThat(mActivity.launchPostureGuidance()).isFalse();
assertThat(mActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_UNKNOWN);
}
@Test
public void testFaceEnrollEducation_onStartNeverRegisterPostureChangeCallback() {
setupActivity();
mActivity.onStart();
assertThat(mActivity.getPostureGuidanceIntent()).isNull();
assertThat(mActivity.getPostureCallback()).isNull();
assertThat(mActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_UNKNOWN);
}
@Test
public void testFaceEnrollEducationWithPosture_onStartRegisteredPostureChangeCallback() {
setupActivityForPosture();
mActivity.onStart();
assertThat(mActivity.getPostureGuidanceIntent()).isNotNull();
assertThat(mActivity.getPostureCallback()).isNotNull();
}
@Test
public void testFaceEnrollEducationWithPosture_onFoldedUpdated_unFolded() {
final Configuration newConfig = new Configuration();
newConfig.smallestScreenWidthDp = DENSITY_XXXHIGH;
setupActivityForPosture();
mActivity.onStart();
assertThat(mActivity.getPostureGuidanceIntent()).isNotNull();
assertThat(mActivity.getPostureCallback()).isNotNull();
mActivity.onConfigurationChanged(newConfig);
assertThat(mActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_OPENED);
}
@Test
public void testFaceEnrollEducationWithPosture_onFoldedUpdated_folded() {
final Configuration newConfig = new Configuration();
newConfig.smallestScreenWidthDp = DENSITY_DEFAULT;
setupActivityForPosture();
mActivity.onStart();
assertThat(mActivity.getPostureGuidanceIntent()).isNotNull();
assertThat(mActivity.getPostureCallback()).isNotNull();
mActivity.onConfigurationChanged(newConfig);
assertThat(mActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_CLOSED);
}
}

View File

@@ -16,32 +16,61 @@
package com.android.settings.biometrics.face;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static android.util.DisplayMetrics.DENSITY_XXXHIGH;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_NEXT_LAUNCHED;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_LAUNCHED_POSTURE_GUIDANCE;
import static com.android.settings.biometrics.BiometricUtils.DEVICE_POSTURE_CLOSED;
import static com.android.settings.biometrics.BiometricUtils.DEVICE_POSTURE_OPENED;
import static com.android.settings.biometrics.BiometricUtils.DEVICE_POSTURE_UNKNOWN;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.face.Face;
import android.hardware.face.FaceManager;
import android.os.UserHandle;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import com.android.settings.testutils.shadow.ShadowSensorPrivacyManager;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
import com.google.android.setupdesign.view.BottomScrollView;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
@@ -57,95 +86,25 @@ import java.util.List;
@Config(shadows = {
ShadowLockPatternUtils.class,
ShadowUserManager.class,
ShadowUtils.class,
ShadowDevicePolicyManager.class,
ShadowSensorPrivacyManager.class
})
public class FaceEnrollIntroductionTest {
@Mock private FaceManager mFaceManager;
@Mock
private FaceManager mFaceManager;
@Mock
private LockPatternUtils mLockPatternUtils;
private ActivityController<TestFaceEnrollIntroduction> mController;
private Context mContext;
private ActivityController<? extends Activity> mController;
private TestFaceEnrollIntroduction mActivity;
private FaceEnrollIntroduction mSpyActivity;
private FakeFeatureFactory mFakeFeatureFactory;
private ShadowUserManager mUserManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
private void setupActivity(@NonNull Intent intent) {
doAnswer(invocation -> {
final FaceManager.GenerateChallengeCallback callback =
invocation.getArgument(1);
callback.onGenerateChallengeResult(0, 0, 1L);
return null;
}).when(mFaceManager).generateChallenge(anyInt(), any());
mController = Robolectric.buildActivity(TestFaceEnrollIntroduction.class, intent);
mActivity = mController.get();
mActivity.mOverrideFaceManager = mFaceManager;
}
private void setFaceManagerToHave(int numEnrollments) {
List<Face> faces = new ArrayList<>();
for (int i = 0; i < numEnrollments; i++) {
faces.add(new Face("Face " + i /* name */, 1 /*faceId */, 1 /* deviceId */));
}
when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(faces);
}
@Test
public void intro_CheckCanEnroll() {
setFaceManagerToHave(0 /* numEnrollments */);
setupActivity(new Intent());
mController.create();
int result = mActivity.checkMaxEnrolled();
assertThat(result).isEqualTo(0);
}
@Test
public void intro_CheckMaxEnrolled() {
setFaceManagerToHave(1 /* numEnrollments */);
setupActivity(new Intent());
mController.create();
int result = mActivity.checkMaxEnrolled();
assertThat(result).isEqualTo(R.string.face_intro_error_max);
}
@Test
public void testOnCreate() {
setupActivity(new Intent());
mController.create();
}
@Test
public void testOnCreateToGenerateChallenge() {
setupActivity(new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L));
mActivity.mGateKeeperAction = GateKeeperAction.RETURN_BYTE_ARRAY;
mController.create();
}
@Test
public void testGenerateChallengeFailThenRecreate() {
setupActivity(new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L));
mActivity.mGateKeeperAction = GateKeeperAction.THROW_CREDENTIAL_NOT_MATCH;
mController.create();
// Make sure recreate() is called on original activity
assertThat(mActivity.getRecreateCount()).isEqualTo(1);
// Simulate recreate() action
setupActivity(mActivity.getIntent());
mController.create();
// Verify confirmLock()
assertThat(mActivity.getConfirmingCredentials()).isTrue();
ShadowActivity shadowActivity = Shadows.shadowOf(mActivity);
ShadowActivity.IntentForResult startedActivity =
shadowActivity.getNextStartedActivityForResult();
assertWithMessage("Next activity 1").that(startedActivity).isNotNull();
}
enum GateKeeperAction { CALL_SUPER, RETURN_BYTE_ARRAY, THROW_CREDENTIAL_NOT_MATCH }
enum GateKeeperAction {CALL_SUPER, RETURN_BYTE_ARRAY, THROW_CREDENTIAL_NOT_MATCH}
public static class TestFaceEnrollIntroduction extends FaceEnrollIntroduction {
@@ -166,14 +125,15 @@ public class FaceEnrollIntroductionTest {
}
public FaceManager mOverrideFaceManager = null;
@NonNull public GateKeeperAction mGateKeeperAction = GateKeeperAction.CALL_SUPER;
@NonNull
public GateKeeperAction mGateKeeperAction = GateKeeperAction.CALL_SUPER;
@Nullable
@Override
public byte[] requestGatekeeperHat(long challenge) {
switch (mGateKeeperAction) {
case RETURN_BYTE_ARRAY:
return new byte[] { 1 };
return new byte[]{1};
case THROW_CREDENTIAL_NOT_MATCH:
throw new BiometricUtils.GatekeeperCredentialNotMatchException("test");
case CALL_SUPER:
@@ -187,5 +147,257 @@ public class FaceEnrollIntroductionTest {
protected FaceManager getFaceManager() {
return mOverrideFaceManager;
}
@Override
protected boolean launchPostureGuidance() {
return super.launchPostureGuidance();
}
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ShadowUtils.setFaceManager(mFaceManager);
mUserManager = ShadowUserManager.getShadow();
mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
when(mFakeFeatureFactory.securityFeatureProvider.getLockPatternUtils(any(Context.class)))
.thenReturn(mLockPatternUtils);
}
@After
public void tearDown() {
ShadowUtils.reset();
}
private void setupActivity() {
final Intent testIntent = new Intent();
// Set the challenge token so the confirm screen will not be shown
testIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]);
when(mFakeFeatureFactory.mFaceFeatureProvider.getPostureGuidanceIntent(any())).thenReturn(
null /* Simulate no posture intent */);
mContext = spy(ApplicationProvider.getApplicationContext());
mUserManager.addUserProfile(new UserHandle(0));
mController = Robolectric.buildActivity(
TestFaceEnrollIntroduction.class, testIntent);
mActivity = (TestFaceEnrollIntroduction) spy(mController.get());
mActivity.mOverrideFaceManager = mFaceManager;
when(mActivity.getPostureGuidanceIntent()).thenReturn(null);
when(mContext.getApplicationContext()).thenReturn(mContext);
when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mLockPatternUtils.getActivePasswordQuality(Mockito.anyInt())).thenReturn(
PASSWORD_QUALITY_NUMERIC);
mController.create();
}
private void setupActivityForPosture() {
final Intent testIntent = new Intent();
// Set the challenge token so the confirm screen will not be shown
testIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]);
testIntent.putExtra(EXTRA_KEY_NEXT_LAUNCHED, false);
testIntent.putExtra(EXTRA_LAUNCHED_POSTURE_GUIDANCE, false);
when(mFakeFeatureFactory.mFaceFeatureProvider.getPostureGuidanceIntent(any())).thenReturn(
testIntent);
mContext = spy(ApplicationProvider.getApplicationContext());
mUserManager.addUserProfile(new UserHandle(0));
mController = Robolectric.buildActivity(TestFaceEnrollIntroduction.class, testIntent);
mSpyActivity = (FaceEnrollIntroduction) spy(mController.get());
when(mSpyActivity.getPostureGuidanceIntent()).thenReturn(testIntent);
when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mLockPatternUtils.getActivePasswordQuality(Mockito.anyInt())).thenReturn(
PASSWORD_QUALITY_NUMERIC);
mController.create();
}
private void setupActivityWithGenerateChallenge(@NonNull Intent intent) {
doAnswer(invocation -> {
final FaceManager.GenerateChallengeCallback callback =
invocation.getArgument(1);
callback.onGenerateChallengeResult(0, 0, 1L);
return null;
}).when(mFaceManager).generateChallenge(anyInt(), any());
mController = Robolectric.buildActivity(TestFaceEnrollIntroduction.class, intent);
mActivity = (TestFaceEnrollIntroduction) mController.get();
mActivity.mOverrideFaceManager = mFaceManager;
}
private GlifLayout getGlifLayout(Activity activity) {
return activity.findViewById(R.id.setup_wizard_layout);
}
private void setFaceManagerToHave(int numEnrollments) {
List<Face> faces = new ArrayList<>();
for (int i = 0; i < numEnrollments; i++) {
faces.add(new Face("Face " + i /* name */, 1 /*faceId */, 1 /* deviceId */));
}
when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(faces);
}
@Test
public void intro_CheckCanEnroll() {
setFaceManagerToHave(0 /* numEnrollments */);
setupActivityWithGenerateChallenge(new Intent());
mController.create();
int result = mActivity.checkMaxEnrolled();
assertThat(result).isEqualTo(0);
}
@Test
public void intro_CheckMaxEnrolled() {
setFaceManagerToHave(1 /* numEnrollments */);
setupActivityWithGenerateChallenge(new Intent());
mController.create();
int result = mActivity.checkMaxEnrolled();
assertThat(result).isEqualTo(R.string.face_intro_error_max);
}
@Test
public void testOnCreate() {
setupActivityWithGenerateChallenge(new Intent());
mController.create();
}
@Test
public void testOnCreateToGenerateChallenge() {
setupActivityWithGenerateChallenge(
new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L));
mActivity.mGateKeeperAction = GateKeeperAction.RETURN_BYTE_ARRAY;
mController.create();
}
@Test
public void testGenerateChallengeFailThenRecreate() {
setupActivityWithGenerateChallenge(
new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L));
mActivity.mGateKeeperAction = GateKeeperAction.THROW_CREDENTIAL_NOT_MATCH;
mController.create();
// Make sure recreate() is called on original activity
assertThat(mActivity.getRecreateCount()).isEqualTo(1);
// Simulate recreate() action
setupActivityWithGenerateChallenge(mActivity.getIntent());
mController.create();
// Verify confirmLock()
assertThat(mActivity.getConfirmingCredentials()).isTrue();
ShadowActivity shadowActivity = Shadows.shadowOf(mActivity);
ShadowActivity.IntentForResult startedActivity =
shadowActivity.getNextStartedActivityForResult();
assertWithMessage("Next activity 1").that(startedActivity).isNotNull();
}
@Test
public void testFaceEnrollIntroduction_hasHeader() {
setupActivity();
TextView headerTextView = getGlifLayout(mActivity).findViewById(R.id.suc_layout_title);
assertThat(headerTextView).isNotNull();
assertThat(headerTextView.getText().toString()).isNotEmpty();
}
@Test
public void testFaceEnrollIntroduction_hasDescription() {
setupActivity();
CharSequence desc = getGlifLayout(mActivity).getDescriptionText();
assertThat(desc.toString()).isEqualTo(
mContext.getString(R.string.security_settings_face_enroll_introduction_message));
}
@Test
public void testFaceEnrollEducation_hasBottomScrollView() {
setupActivity();
BottomScrollView scrollView = getGlifLayout(mActivity).findViewById(R.id.sud_scroll_view);
assertThat(scrollView).isNotNull();
assertThat(scrollView.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void testFaceEnrollIntroduction_showFooterPrimaryButton() {
setupActivity();
FooterBarMixin footer = getGlifLayout(mActivity).getMixin(FooterBarMixin.class);
FooterButton footerButton = footer.getPrimaryButton();
assertThat(footerButton).isNotNull();
assertThat(footerButton.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(footerButton.getText().toString()).isEqualTo(
mContext.getString(R.string.security_settings_face_enroll_introduction_agree));
}
@Test
public void testFaceEnrollIntroduction_notShowFooterSecondaryButton() {
setupActivity();
FooterBarMixin footer = getGlifLayout(mActivity).getMixin(FooterBarMixin.class);
FooterButton footerButton = footer.getSecondaryButton();
assertThat(footerButton.getVisibility()).isEqualTo(View.INVISIBLE);
}
@Test
public void testFaceEnrollIntroduction_defaultNeverLaunchPostureGuidance() {
setupActivity();
assertThat(mActivity.launchPostureGuidance()).isFalse();
assertThat(mActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_UNKNOWN);
}
@Test
public void testFaceEnrollIntroduction_onStartNeverRegisterPostureChangeCallback() {
setupActivity();
mActivity.onStart();
assertThat(mActivity.getPostureGuidanceIntent()).isNull();
assertThat(mActivity.getPostureCallback()).isNull();
assertThat(mActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_UNKNOWN);
}
@Test
public void testFaceEnrollIntroduction_onStartRegisteredPostureChangeCallback() {
setupActivityForPosture();
mSpyActivity.onStart();
assertThat(mSpyActivity.getPostureGuidanceIntent()).isNotNull();
assertThat(mSpyActivity.getPostureCallback()).isNotNull();
}
@Test
public void testFaceEnrollIntroduction_onFoldedUpdated_unFolded() {
final Configuration newConfig = new Configuration();
newConfig.smallestScreenWidthDp = DENSITY_XXXHIGH;
setupActivityForPosture();
mSpyActivity.onStart();
assertThat(mSpyActivity.getPostureGuidanceIntent()).isNotNull();
assertThat(mSpyActivity.getPostureCallback()).isNotNull();
mSpyActivity.onConfigurationChanged(newConfig);
assertThat(mSpyActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_OPENED);
}
@Test
public void testFaceEnrollEducation_onFoldedUpdated_folded() {
final Configuration newConfig = new Configuration();
newConfig.smallestScreenWidthDp = DENSITY_DEFAULT;
setupActivityForPosture();
mSpyActivity.onStart();
assertThat(mSpyActivity.getPostureGuidanceIntent()).isNotNull();
assertThat(mSpyActivity.getPostureCallback()).isNotNull();
mSpyActivity.onConfigurationChanged(newConfig);
assertThat(mSpyActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_CLOSED);
}
}