diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java index 7c5c77b5350..a19138e2673 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java @@ -185,7 +185,9 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase mUserManager = getUserManager(); updatePasswordQuality(); - if (!mConfirmingCredentials) { + // Check isFinishing() because FaceEnrollIntroduction may finish self to launch + // FaceSettings during onCreate() + if (!mConfirmingCredentials && !isFinishing()) { if (!mHasPassword) { // No password registered, launch into enrollment wizard. mConfirmingCredentials = true; diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java index 46c392ed408..f434a67f485 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java @@ -28,6 +28,7 @@ import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.face.FaceManager; import android.os.Bundle; +import android.os.UserHandle; import android.text.Html; import android.text.method.LinkMovementMethod; import android.util.Log; @@ -42,6 +43,7 @@ import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import com.android.settings.R; +import com.android.settings.Settings; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollActivity; import com.android.settings.biometrics.BiometricEnrollIntroduction; @@ -110,8 +112,26 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { @Override protected void onCreate(Bundle savedInstanceState) { + mFaceManager = getFaceManager(); + + if (savedInstanceState == null + && !WizardManagerHelper.isAnySetupWizard(getIntent()) + && !getIntent().getBooleanExtra(EXTRA_FROM_SETTINGS_SUMMARY, false) + && maxFacesEnrolled()) { + // from tips && maxEnrolled + Log.d(TAG, "launch face settings"); + launchFaceSettingsActivity(); + finish(); + } + super.onCreate(savedInstanceState); + // Wait super::onCreated() then return because SuperNotCalledExceptio will be thrown + // if we don't wait for it. + if (isFinishing()) { + return; + } + // Apply extracted theme color to icons. final ImageView iconGlasses = findViewById(R.id.icon_glasses); final ImageView iconLooking = findViewById(R.id.icon_looking); @@ -152,8 +172,6 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { infoMessageRequireEyes.setText(getInfoMessageRequireEyes()); } - mFaceManager = getFaceManager(); - // This path is an entry point for SetNewPasswordController, e.g. // adb shell am start -a android.app.action.SET_NEW_PASSWORD if (mToken == null && BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { @@ -191,6 +209,24 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { Log.v(TAG, "cameraPrivacyEnabled : " + cameraPrivacyEnabled); } + private void launchFaceSettingsActivity() { + final Intent intent = new Intent(this, Settings.FaceSettingsInternalActivity.class); + final byte[] token = getIntent().getByteArrayExtra( + ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); + if (token != null) { + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); + } + final int userId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); + if (userId != UserHandle.USER_NULL) { + intent.putExtra(Intent.EXTRA_USER_ID, userId); + } + BiometricUtils.copyMultiBiometricExtras(getIntent(), intent); + intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true); + intent.putExtra(EXTRA_KEY_CHALLENGE, getIntent().getLongExtra(EXTRA_KEY_CHALLENGE, -1L)); + intent.putExtra(EXTRA_KEY_SENSOR_ID, getIntent().getIntExtra(EXTRA_KEY_SENSOR_ID, -1)); + startActivity(intent); + } + @VisibleForTesting @Nullable protected FaceManager getFaceManager() { @@ -232,6 +268,15 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { @Override protected void onStart() { super.onStart(); + listenFoldEventForPostureGuidance(); + } + + private void listenFoldEventForPostureGuidance() { + if (maxFacesEnrolled()) { + Log.d(TAG, "Device has enrolled face, do not show posture guidance"); + return; + } + if (getPostureGuidanceIntent() == null) { Log.d(TAG, "Device do not support posture guidance"); return; diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java index 1dd67c1b18e..591f88d79fa 100644 --- a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java @@ -51,6 +51,8 @@ import androidx.test.core.app.ApplicationProvider; import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; +import com.android.settings.Settings; +import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricUtils; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.testutils.FakeFeatureFactory; @@ -62,6 +64,7 @@ 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.setupcompat.util.WizardManagerHelper; import com.google.android.setupdesign.GlifLayout; import com.google.android.setupdesign.view.BottomScrollView; @@ -168,6 +171,7 @@ public class FaceEnrollIntroductionTest { @After public void tearDown() { ShadowUtils.reset(); + ShadowLockPatternUtils.reset(); } private void setupActivity() { @@ -315,7 +319,7 @@ public class FaceEnrollIntroductionTest { } @Test - public void testFaceEnrollEducation_hasBottomScrollView() { + public void testFaceEnrollIntroduction_hasBottomScrollView() { setupActivity(); BottomScrollView scrollView = getGlifLayout(mActivity).findViewById(R.id.sud_scroll_view); @@ -387,7 +391,7 @@ public class FaceEnrollIntroductionTest { } @Test - public void testFaceEnrollEducation_onFoldedUpdated_folded() { + public void testFaceEnrollIntroduction_onFoldedUpdated_folded() { final Configuration newConfig = new Configuration(); newConfig.smallestScreenWidthDp = DENSITY_DEFAULT; setupActivityForPosture(); @@ -400,4 +404,97 @@ public class FaceEnrollIntroductionTest { assertThat(mSpyActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_CLOSED); } + + @Test + public void testFaceEnrollIntroduction_maxFacesEnrolled_launchFaceSettings() { + setFaceManagerToHave(1 /* numEnrollments */); + final Intent intent = new Intent(); + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]); + mController = Robolectric.buildActivity(TestFaceEnrollIntroduction.class, intent); + mActivity = (TestFaceEnrollIntroduction) mController.get(); + mActivity.mOverrideFaceManager = mFaceManager; + + mController.create(); + + ShadowActivity shadowActivity = Shadows.shadowOf(mActivity); + final Intent nextStartedActivity = shadowActivity.getNextStartedActivity(); + assertThat(nextStartedActivity).isNotNull(); + assertThat(nextStartedActivity.getComponent().getClassName()) + .isEqualTo(Settings.FaceSettingsInternalActivity.class.getName()); + } + + @Test + public void testFaceEnrollIntroduction_maxFacesEnrolled_isSuw_notLaunchFaceSettings() { + setFaceManagerToHave(1 /* numEnrollments */); + ShadowLockPatternUtils.setActivePasswordQuality(PASSWORD_QUALITY_NUMERIC); + final Intent intent = new Intent(); + intent.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true); + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]); + mController = Robolectric.buildActivity(TestFaceEnrollIntroduction.class, intent); + mActivity = (TestFaceEnrollIntroduction) mController.get(); + mActivity.mOverrideFaceManager = mFaceManager; + + mController.create(); + + ShadowActivity shadowActivity = Shadows.shadowOf(mActivity); + final Intent nextStartedActivity = shadowActivity.getNextStartedActivity(); + assertThat(nextStartedActivity).isNull(); + } + + @Test + public void testFaceEnrollIntroduction_maxFacesEnrolled_fromSettings_notLaunchFaceSettings() { + setFaceManagerToHave(1 /* numEnrollments */); + ShadowLockPatternUtils.setActivePasswordQuality(PASSWORD_QUALITY_NUMERIC); + final Intent intent = new Intent(); + intent.putExtra(BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY, true); + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]); + mController = Robolectric.buildActivity(TestFaceEnrollIntroduction.class, intent); + mActivity = (TestFaceEnrollIntroduction) mController.get(); + mActivity.mOverrideFaceManager = mFaceManager; + + mController.create(); + + ShadowActivity shadowActivity = Shadows.shadowOf(mActivity); + final Intent nextStartedActivity = shadowActivity.getNextStartedActivity(); + assertThat(nextStartedActivity).isNull(); + } + + @Test + public void testFaceEnrollIntroduction_hasPostureCallback() { + when(mFakeFeatureFactory.mFaceFeatureProvider.getPostureGuidanceIntent(any())) + .thenReturn(new Intent()); + setFaceManagerToHave(0 /* numEnrollments */); + ShadowLockPatternUtils.setActivePasswordQuality(PASSWORD_QUALITY_NUMERIC); + final Intent intent = new Intent(); + intent.putExtra(BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY, true); + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]); + mController = Robolectric.buildActivity(TestFaceEnrollIntroduction.class, intent); + mActivity = (TestFaceEnrollIntroduction) mController.get(); + mActivity.mOverrideFaceManager = mFaceManager; + + mController.create(); + mController.start(); + + assertThat(mActivity.getPostureCallback()).isNotNull(); + } + + @Test + public void testFaceEnrollIntroduction_maxFacesEnrolled_noPostureCallback() { + when(mFakeFeatureFactory.mFaceFeatureProvider.getPostureGuidanceIntent(any())) + .thenReturn(new Intent()); + setFaceManagerToHave(1 /* numEnrollments */); + ShadowLockPatternUtils.setActivePasswordQuality(PASSWORD_QUALITY_NUMERIC); + final Intent intent = new Intent(); + intent.putExtra(BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY, true); + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]); + mController = Robolectric.buildActivity(TestFaceEnrollIntroduction.class, intent); + mActivity = (TestFaceEnrollIntroduction) mController.get(); + mActivity.mOverrideFaceManager = mFaceManager; + + mController.create(); + mController.start(); + + assertThat(mActivity.getPostureCallback()).isNull(); + } + } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java index c6c6c556818..f24b2957724 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java @@ -40,6 +40,7 @@ import java.util.Map; public class ShadowLockPatternUtils { private static boolean sDeviceEncryptionEnabled; + private static Map sUserToActivePasswordQualityMap = new HashMap<>(); private static Map sUserToComplexityMap = new HashMap<>(); private static Map sUserToProfileComplexityMap = new HashMap<>(); private static Map sUserToMetricsMap = new HashMap<>(); @@ -54,6 +55,7 @@ public class ShadowLockPatternUtils { @Resetter public static void reset() { + sUserToActivePasswordQualityMap.clear(); sUserToComplexityMap.clear(); sUserToProfileComplexityMap.clear(); sUserToMetricsMap.clear(); @@ -87,7 +89,11 @@ public class ShadowLockPatternUtils { @Implementation protected int getActivePasswordQuality(int userId) { - return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; + final Integer activePasswordQuality = sUserToActivePasswordQualityMap.get(userId); + if (activePasswordQuality == null) { + return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; + } + return activePasswordQuality; } @Implementation @@ -227,6 +233,10 @@ public class ShadowLockPatternUtils { sUserToProfileMetricsMap.put(UserHandle.myUserId(), metrics); } + public static void setActivePasswordQuality(int quality) { + sUserToActivePasswordQualityMap.put(UserHandle.myUserId(), quality); + } + @Implementation public boolean isLockScreenDisabled(int userId) { return false;