From c7dd729a43a7b9278b0a45912a74739633bbbb90 Mon Sep 17 00:00:00 2001 From: lbill Date: Sat, 4 Feb 2023 04:02:35 +0000 Subject: [PATCH] Launch Face Settings when device enrolled face MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In legacy flow FaceEnrollIntroduction check maxFacesEnrolled() then update introduction description and prevent user go to next enroll step, the CUJ was not good to user, instead bring user to Face Settings and let user decide the next action(Delete face or change config) is more make sense. As any applications could broadcast intent(android.settings.FACE_ENROLL) to bring FaceEnrollIntroduction up, we add a check in onCreate() and check if device has been enrolled max face, launch FaceSettingsInternalActivity. In addition, skip to register any posture change callback when maxFacesEnrolled() during activity onStart(). Test: atest SettingsGoogleUnitTests Test: m -j SettingsGoogleRoboTests RunSettingsGoogleRoboTests Test: m RunSettingsRoboTests ROBOTEST_FILTER= \       "FaceEnrollIntroductionTest" Test: m RunSettingsRoboTests ROBOTEST_FILTER= \       "FingerprintEnrollIntroductionTest" Test: Manual enrolled face, unfold device, launch Tips Security page and click "Set up Face Unlock", device launch Face Settings insteand of posture guidance page. Bug: 263830403 Change-Id: Ied8b92259810f954ce6b2daaa9b87fc996ad752a --- .../BiometricEnrollIntroduction.java | 4 +- .../face/FaceEnrollIntroduction.java | 49 ++++++++- .../face/FaceEnrollIntroductionTest.java | 101 +++++++++++++++++- .../shadow/ShadowLockPatternUtils.java | 12 ++- 4 files changed, 160 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java index 730e0496774..cdd2d8b88d9 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java @@ -183,7 +183,9 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase mUserManager = UserManager.get(this); 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 a1233086926..66ed56afc96 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 6c04add9261..9d588557199 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 74e39715062..9a83b5919ee 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java @@ -36,6 +36,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<>(); @@ -44,6 +45,7 @@ public class ShadowLockPatternUtils { @Resetter public static void reset() { + sUserToActivePasswordQualityMap.clear(); sUserToComplexityMap.clear(); sUserToProfileComplexityMap.clear(); sUserToMetricsMap.clear(); @@ -72,7 +74,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 @@ -153,6 +159,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;