Launch Face Settings when device enrolled face

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
diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
index 730e049..cdd2d8b 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
@@ -183,7 +183,9 @@
         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 a123308..66ed56a 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.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.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 @@
 
     @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 @@
             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 @@
         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 @@
     @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 6c04add..9d58855 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 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.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 @@
     @After
     public void tearDown() {
         ShadowUtils.reset();
+        ShadowLockPatternUtils.reset();
     }
 
     private void setupActivity() {
@@ -315,7 +319,7 @@
     }
 
     @Test
-    public void testFaceEnrollEducation_hasBottomScrollView() {
+    public void testFaceEnrollIntroduction_hasBottomScrollView() {
         setupActivity();
         BottomScrollView scrollView = getGlifLayout(mActivity).findViewById(R.id.sud_scroll_view);
 
@@ -387,7 +391,7 @@
     }
 
     @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 @@
 
         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 74e3971..9a83b59 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 @@
 public class ShadowLockPatternUtils {
 
     private static boolean sDeviceEncryptionEnabled;
+    private static Map<Integer, Integer> sUserToActivePasswordQualityMap = new HashMap<>();
     private static Map<Integer, Integer> sUserToComplexityMap = new HashMap<>();
     private static Map<Integer, Integer> sUserToProfileComplexityMap = new HashMap<>();
     private static Map<Integer, PasswordMetrics> sUserToMetricsMap = new HashMap<>();
@@ -44,6 +45,7 @@
 
     @Resetter
     public static void reset() {
+        sUserToActivePasswordQualityMap.clear();
         sUserToComplexityMap.clear();
         sUserToProfileComplexityMap.clear();
         sUserToMetricsMap.clear();
@@ -72,7 +74,11 @@
 
     @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 @@
         sUserToProfileMetricsMap.put(UserHandle.myUserId(), metrics);
     }
 
+    public static void setActivePasswordQuality(int quality) {
+        sUserToActivePasswordQualityMap.put(UserHandle.myUserId(), quality);
+    }
+
     @Implementation
     public boolean isLockScreenDisabled(int userId) {
         return false;