Merge "[Settings] Apply the SettingsDataService to the SIM page, MobileDataPreferenceController"
diff --git a/res/values/config.xml b/res/values/config.xml
index d7b2afa..e7efa6f 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -615,6 +615,9 @@
         <item>android.uid.system:1000</item>
     </string-array>
 
+    <!-- The default value for RedactionInterstitial in SUW -->
+    <bool name="default_allow_sensitive_lockscreen_content">true</bool>
+
     <!-- Whether to enable the app battery usage list page feature. -->
     <bool name="config_app_battery_usage_list_enabled">false</bool>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2dc4e65..0fd9617 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -10486,8 +10486,8 @@
     <!-- Title for the button to reboot with MTE enabled. [CHAR LIMIT=NONE] -->
     <string name="reboot_with_mte_title">Reboot with MTE</string>
     <string name="reboot_with_mte_message">System will reboot and allow to experiment with Memory Tagging Extension (MTE). MTE may negatively impact system performance and stability. Will be reset on next subsequent reboot.</string>
-    <string name="reboot_with_mte_summary">Try MTE for a single boot for app development.</string>
-    <string name="reboot_with_mte_already_enabled">MTE is enabled through Advanced memory protection.</string>
+    <string name="reboot_with_mte_summary">Try MTE for a single boot for app development</string>
+    <string name="reboot_with_mte_already_enabled">MTE is enabled through Advanced memory protection</string>
     <!-- Toast that is shown when the user initiates capturing a heap dump for the system server. [CHAR LIMIT=NONE] -->
     <string name="capturing_system_heap_dump_message">Capturing system heap dump</string>
     <!-- Toast that is shown if there's an error capturing the user initiated heap dump. [CHAR LIMIT=NONE] -->
diff --git a/src/com/android/settings/TrustedCredentialsFragment.java b/src/com/android/settings/TrustedCredentialsFragment.java
index ca565a4..c90a44d 100644
--- a/src/com/android/settings/TrustedCredentialsFragment.java
+++ b/src/com/android/settings/TrustedCredentialsFragment.java
@@ -25,7 +25,6 @@
 import android.app.Activity;
 import android.app.KeyguardManager;
 import android.app.admin.DevicePolicyManager;
-import android.app.settings.SettingsEnums;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -68,7 +67,7 @@
 import com.android.internal.app.UnlaunchableAppActivity;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settings.TrustedCredentialsSettings.Tab;
-import com.android.settings.core.InstrumentedFragment;
+import com.android.settingslib.core.lifecycle.ObservableFragment;
 
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
@@ -81,7 +80,7 @@
 /**
  * Fragment to display trusted credentials settings for one tab.
  */
-public class TrustedCredentialsFragment extends InstrumentedFragment
+public class TrustedCredentialsFragment extends ObservableFragment
         implements TrustedCredentialsDialogBuilder.DelegateInterface {
 
     public static final String ARG_POSITION = "tab";
@@ -176,11 +175,6 @@
         return mFragmentView;
     }
 
-    @Override
-    public int getMetricsCategory() {
-        return SettingsEnums.TRUSTED_CREDENTIALS;
-    }
-
     private void createChildView(
             LayoutInflater inflater, ViewGroup parent, Bundle childState, int i) {
         boolean isWork = mGroupAdapter.getUserInfoByGroup(i).isManagedProfile();
diff --git a/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java b/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java
index 4251346..d17f843 100644
--- a/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java
+++ b/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java
@@ -113,7 +113,7 @@
 
     @Override
     public int getMetricsCategory() {
-        return SettingsEnums.SYSTEM_ALERT_WINDOW_APPS;
+        return SettingsEnums.MODIFY_SYSTEM_SETTINGS;
     }
 
     public static CharSequence getSummary(Context context, AppEntry entry) {
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index fa5852e..4701d0d 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -527,7 +527,7 @@
             case LIST_TYPE_OVERLAY:
                 return SettingsEnums.SYSTEM_ALERT_WINDOW_APPS;
             case LIST_TYPE_WRITE_SETTINGS:
-                return SettingsEnums.SYSTEM_ALERT_WINDOW_APPS;
+                return SettingsEnums.MODIFY_SYSTEM_SETTINGS;
             case LIST_TYPE_MANAGE_SOURCES:
                 return SettingsEnums.MANAGE_EXTERNAL_SOURCES;
             case LIST_TYPE_WIFI_ACCESS:
diff --git a/src/com/android/settings/biometrics2/factory/BiometricsFragmentFactory.java b/src/com/android/settings/biometrics2/factory/BiometricsFragmentFactory.java
deleted file mode 100644
index 9a0cab2..0000000
--- a/src/com/android/settings/biometrics2/factory/BiometricsFragmentFactory.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 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.biometrics2.factory;
-
-import android.app.Application;
-import android.app.admin.DevicePolicyManager;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentFactory;
-import androidx.lifecycle.ViewModelProvider;
-
-import com.android.settings.biometrics2.ui.view.FingerprintEnrollIntroFragment;
-
-/**
- * Fragment factory for biometrics
- */
-public class BiometricsFragmentFactory extends FragmentFactory {
-
-    private final Application mApplication;
-    private final ViewModelProvider mViewModelProvider;
-
-    public BiometricsFragmentFactory(Application application,
-            ViewModelProvider viewModelProvider) {
-        mApplication = application;
-        mViewModelProvider = viewModelProvider;
-    }
-
-    @NonNull
-    @Override
-    public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
-        final Class<? extends Fragment> clazz = loadFragmentClass(classLoader, className);
-        if (FingerprintEnrollIntroFragment.class.equals(clazz)) {
-            final DevicePolicyManager devicePolicyManager =
-                    mApplication.getSystemService(DevicePolicyManager.class);
-            if (devicePolicyManager != null) {
-                return new FingerprintEnrollIntroFragment(mViewModelProvider,
-                        devicePolicyManager.getResources());
-            }
-        }
-        return super.instantiate(classLoader, className);
-    }
-}
diff --git a/src/com/android/settings/biometrics2/ui/model/CredentialModel.java b/src/com/android/settings/biometrics2/ui/model/CredentialModel.java
index 06caf5e..b943608 100644
--- a/src/com/android/settings/biometrics2/ui/model/CredentialModel.java
+++ b/src/com/android/settings/biometrics2/ui/model/CredentialModel.java
@@ -22,6 +22,7 @@
 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
 
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
@@ -80,18 +81,31 @@
     @Nullable
     private Long mClearGkPwHandleMillis = null;
 
-    public CredentialModel(@NonNull Intent intent, @NonNull Clock clock) {
-        mUserId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
-        mSensorId = intent.getIntExtra(EXTRA_KEY_SENSOR_ID, INVALID_SENSOR_ID);
-        mChallenge = intent.getLongExtra(EXTRA_KEY_CHALLENGE, INVALID_CHALLENGE);
-        mToken = intent.getByteArrayExtra(EXTRA_KEY_CHALLENGE_TOKEN);
-        mGkPwHandle = intent.getLongExtra(EXTRA_KEY_GK_PW_HANDLE,
-                INVALID_GK_PW_HANDLE);
+    public CredentialModel(@NonNull Bundle bundle, @NonNull Clock clock) {
+        mUserId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId());
+        mSensorId = bundle.getInt(EXTRA_KEY_SENSOR_ID, INVALID_SENSOR_ID);
+        mChallenge = bundle.getLong(EXTRA_KEY_CHALLENGE, INVALID_CHALLENGE);
+        mToken = bundle.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN);
+        mGkPwHandle = bundle.getLong(EXTRA_KEY_GK_PW_HANDLE, INVALID_GK_PW_HANDLE);
         mClock = clock;
         mInitMillis = mClock.millis();
     }
 
     /**
+     * Get a bundle which can be used to recreate CredentialModel
+     */
+    @NonNull
+    public Bundle getBundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putInt(Intent.EXTRA_USER_ID, mUserId);
+        bundle.putInt(EXTRA_KEY_SENSOR_ID, mSensorId);
+        bundle.putLong(EXTRA_KEY_CHALLENGE, mChallenge);
+        bundle.putByteArray(EXTRA_KEY_CHALLENGE_TOKEN, mToken);
+        bundle.putLong(EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle);
+        return bundle;
+    }
+
+    /**
      * Get userId for this credential
      */
     public int getUserId() {
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.java
index 2308f2e..14d859d 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.java
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.java
@@ -25,7 +25,7 @@
 import static com.google.android.setupdesign.util.DynamicColorPalette.ColorType.ACCENT;
 
 import android.app.Activity;
-import android.app.admin.DevicePolicyResourcesManager;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
@@ -63,9 +63,6 @@
 
     private static final String TAG = "FingerprintEnrollIntroFragment";
 
-    @NonNull private final ViewModelProvider mViewModelProvider;
-    @Nullable private final DevicePolicyResourcesManager mDevicePolicyMgrRes;
-
     private FingerprintEnrollIntroViewModel mViewModel = null;
 
     private View mView = null;
@@ -75,12 +72,8 @@
     private TextView mFooterMessage6 = null;
     @Nullable private PorterDuffColorFilter mIconColorFilter;
 
-    public FingerprintEnrollIntroFragment(
-            @NonNull ViewModelProvider viewModelProvider,
-            @Nullable DevicePolicyResourcesManager devicePolicyMgrRes) {
+    public FingerprintEnrollIntroFragment() {
         super();
-        mViewModelProvider = viewModelProvider;
-        mDevicePolicyMgrRes = devicePolicyMgrRes;
     }
 
     @Nullable
@@ -197,7 +190,8 @@
 
     @Override
     public void onAttach(@NonNull Context context) {
-        mViewModel = mViewModelProvider.get(FingerprintEnrollIntroViewModel.class);
+        mViewModel = new ViewModelProvider(getActivity())
+                .get(FingerprintEnrollIntroViewModel.class);
         getLifecycle().addObserver(mViewModel);
         super.onAttach(context);
     }
@@ -232,12 +226,16 @@
     private String getDescriptionDisabledByAdmin(@NonNull Context context) {
         final int defaultStrId =
                 R.string.security_settings_fingerprint_enroll_introduction_message_unlock_disabled;
-        if (mDevicePolicyMgrRes == null) {
+
+        final DevicePolicyManager devicePolicyManager = getActivity()
+                .getSystemService(DevicePolicyManager.class);
+        if (devicePolicyManager != null) {
+            return devicePolicyManager.getResources().getString(FINGERPRINT_UNLOCK_DISABLED,
+                    () -> context.getString(defaultStrId));
+        } else {
             Log.w(TAG, "getDescriptionDisabledByAdmin, null device policy manager res");
             return "";
         }
-        return mDevicePolicyMgrRes.getString(FINGERPRINT_UNLOCK_DISABLED,
-                () -> context.getString(defaultStrId));
     }
 
     private void setHeaderText(@NonNull Activity activity, int resId) {
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java
index e9cf6fd..991aeba 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java
@@ -19,20 +19,19 @@
 import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY;
 
 import static com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR;
-import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE;
 import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK;
 import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK;
+import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE;
+import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID;
 import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL;
 import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH;
 import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL;
 
-import android.app.Activity;
 import android.app.Application;
 import android.content.Intent;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
 import android.os.Bundle;
-import android.os.SystemClock;
 import android.util.Log;
 
 import androidx.activity.result.ActivityResult;
@@ -42,7 +41,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
 import androidx.lifecycle.ViewModelProvider;
 import androidx.lifecycle.viewmodel.CreationExtras;
 import androidx.lifecycle.viewmodel.MutableCreationExtras;
@@ -51,11 +49,9 @@
 import com.android.settings.Utils;
 import com.android.settings.biometrics.BiometricEnrollBase;
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
-import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollEnrolling;
+import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor;
 import com.android.settings.biometrics2.data.repository.FingerprintRepository;
-import com.android.settings.biometrics2.factory.BiometricsFragmentFactory;
 import com.android.settings.biometrics2.factory.BiometricsViewModelFactory;
-import com.android.settings.biometrics2.ui.model.CredentialModel;
 import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
 import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel;
 import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.FingerprintChallengeGenerator;
@@ -102,12 +98,12 @@
         getLifecycle().addObserver(mViewModel);
 
         mAutoCredentialViewModel = viewModelProvider.get(AutoCredentialViewModel.class);
-        mAutoCredentialViewModel.setCredentialModel(new CredentialModel(getIntent(),
-                SystemClock.elapsedRealtimeClock()));
-        getLifecycle().addObserver(mAutoCredentialViewModel);
+        mAutoCredentialViewModel.setCredentialModel(savedInstanceState, getIntent());
+        mAutoCredentialViewModel.getGenerateChallengeFailLiveData().observe(this,
+                this::onGenerateChallengeFail);
 
         mViewModel.getSetResultLiveData().observe(this, this::onSetActivityResult);
-        mAutoCredentialViewModel.getActionLiveData().observe(this, this::onCredentialAction);
+        checkCredential();
 
         // Theme
         setTheme(mViewModel.getRequest().getTheme());
@@ -116,21 +112,29 @@
 
         // fragment
         setContentView(R.layout.biometric_enrollment_container);
-        final FragmentManager fragmentManager = getSupportFragmentManager();
-        fragmentManager.setFragmentFactory(
-                new BiometricsFragmentFactory(getApplication(), viewModelProvider));
 
         final FingerprintEnrollIntroViewModel fingerprintEnrollIntroViewModel =
                 viewModelProvider.get(FingerprintEnrollIntroViewModel.class);
         fingerprintEnrollIntroViewModel.setEnrollmentRequest(mViewModel.getRequest());
         fingerprintEnrollIntroViewModel.setUserId(mAutoCredentialViewModel.getUserId());
+
+        // Clear ActionLiveData in FragmentViewModel to prevent getting previous action when
+        // recreate
+        fingerprintEnrollIntroViewModel.clearActionLiveData();
         fingerprintEnrollIntroViewModel.getActionLiveData().observe(
                 this, this::observeIntroAction);
-        final String tag = "FingerprintEnrollIntroFragment";
-        fragmentManager.beginTransaction()
-                .setReorderingAllowed(true)
-                .add(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null, tag)
-                .commit();
+        if (savedInstanceState == null) {
+            final String tag = "FingerprintEnrollIntroFragment";
+            getSupportFragmentManager().beginTransaction()
+                    .setReorderingAllowed(true)
+                    .add(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null,
+                            tag)
+                    .commit();
+        }
+    }
+
+    private void onGenerateChallengeFail(@NonNull Boolean isFail) {
+        onSetActivityResult(new ActivityResult(RESULT_CANCELED, null));
     }
 
     private void onSetActivityResult(@NonNull ActivityResult result) {
@@ -141,8 +145,8 @@
         finish();
     }
 
-    private void onCredentialAction(@NonNull Integer action) {
-        switch (action) {
+    private void checkCredential() {
+        switch (mAutoCredentialViewModel.checkCredential()) {
             case CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK: {
                 final Intent intent = mAutoCredentialViewModel.getChooseLockIntent(this,
                         mViewModel.getRequest().isSuw(), mViewModel.getRequest().getSuwExtras());
@@ -168,12 +172,9 @@
                 }
                 return;
             }
-            case CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE: {
-                Log.w(TAG, "observeCredentialLiveData, finish with action:" + action);
-                if (mViewModel.getRequest().isAfterSuwOrSuwSuggestedAction()) {
-                    setResult(Activity.RESULT_CANCELED);
-                }
-                finish();
+            case CREDENTIAL_VALID:
+            case CREDENTIAL_IS_GENERATING_CHALLENGE: {
+                // Do nothing
             }
         }
     }
@@ -186,10 +187,15 @@
         if (mAutoCredentialViewModel.checkNewCredentialFromActivityResult(
                 isChooseLock, activityResult)) {
             overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
+        } else {
+            onSetActivityResult(activityResult);
         }
     }
 
-    private void observeIntroAction(@NonNull Integer action) {
+    private void observeIntroAction(@Nullable Integer action) {
+        if (action == null) {
+            return;
+        }
         switch (action) {
             case FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH: {
                 onSetActivityResult(
@@ -207,9 +213,9 @@
                     Log.w(TAG, "startNext, isSuw:" + isSuw + ", fail to set isWaiting flag");
                 }
                 final Intent intent = new Intent(this, isSuw
-                        ? SetupFingerprintEnrollEnrolling.class
+                        ? SetupFingerprintEnrollFindSensor.class
                         : FingerprintEnrollFindSensor.class);
-                intent.putExtras(mAutoCredentialViewModel.getCredentialBundle());
+                intent.putExtras(mAutoCredentialViewModel.getCredentialIntentExtra());
                 intent.putExtras(mViewModel.getNextActivityBaseIntentExtras());
                 mNextActivityLauncher.launch(intent);
             }
@@ -272,5 +278,6 @@
     protected void onSaveInstanceState(@NonNull Bundle outState) {
         super.onSaveInstanceState(outState);
         mViewModel.onSaveInstanceState(outState);
+        mAutoCredentialViewModel.onSaveInstanceState(outState);
     }
 }
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java
index b1a7f90..a443e69 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java
@@ -30,20 +30,21 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.util.Log;
 
 import androidx.activity.result.ActivityResult;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.settings.biometrics.BiometricUtils;
+import com.android.settings.biometrics.BiometricUtils.GatekeeperCredentialNotMatchException;
 import com.android.settings.biometrics2.data.repository.FingerprintRepository;
 import com.android.settings.biometrics2.ui.model.CredentialModel;
 import com.android.settings.password.ChooseLockGeneric;
@@ -57,31 +58,40 @@
  * AutoCredentialViewModel which uses CredentialModel to determine next actions for activity, like
  * start ChooseLockActivity, start ConfirmLockActivity, GenerateCredential, or do nothing.
  */
-public class AutoCredentialViewModel extends AndroidViewModel implements DefaultLifecycleObserver {
+public class AutoCredentialViewModel extends AndroidViewModel {
 
     private static final String TAG = "AutoCredentialViewModel";
-    private static final boolean DEBUG = true;
+
+    @VisibleForTesting
+    static final String KEY_CREDENTIAL_MODEL = "credential_model";
+
+    private static final boolean DEBUG = false;
+
+    /**
+     * Valid credential, activity doesn't need to do anything.
+     */
+    public static final int CREDENTIAL_VALID = 0;
+
+    /**
+     * This credential looks good, but still need to run generateChallenge().
+     */
+    public static final int CREDENTIAL_IS_GENERATING_CHALLENGE = 1;
 
     /**
      * Need activity to run choose lock
      */
-    public static final int CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK = 1;
+    public static final int CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK = 2;
 
     /**
      * Need activity to run confirm lock
      */
-    public static final int CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK = 2;
-
-    /**
-     * Fail to use challenge from hardware generateChallenge(), shall finish activity with proper
-     * error code
-     */
-    public static final int CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE = 3;
+    public static final int CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK = 3;
 
     @IntDef(prefix = { "CREDENTIAL_" }, value = {
+            CREDENTIAL_VALID,
+            CREDENTIAL_IS_GENERATING_CHALLENGE,
             CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK,
-            CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK,
-            CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE
+            CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CredentialAction {}
@@ -157,11 +167,10 @@
         }
     }
 
-
     @NonNull private final LockPatternUtils mLockPatternUtils;
     @NonNull private final ChallengeGenerator mChallengeGenerator;
     private CredentialModel mCredentialModel = null;
-    @NonNull private final MutableLiveData<Integer> mActionLiveData =
+    @NonNull private final MutableLiveData<Boolean> mGenerateChallengeFailLiveData =
             new MutableLiveData<>();
 
     public AutoCredentialViewModel(
@@ -173,51 +182,63 @@
         mChallengeGenerator = challengeGenerator;
     }
 
-    public void setCredentialModel(@NonNull CredentialModel credentialModel) {
-        mCredentialModel = credentialModel;
+    /**
+     * Set CredentialModel, the source is coming from savedInstanceState or activity intent
+     */
+    public void setCredentialModel(@Nullable Bundle savedInstanceState, @NonNull Intent intent) {
+        mCredentialModel = new CredentialModel(
+                savedInstanceState != null
+                        ? savedInstanceState.getBundle(KEY_CREDENTIAL_MODEL)
+                        : intent.getExtras(),
+                SystemClock.elapsedRealtimeClock());
+
+        if (DEBUG) {
+            Log.d(TAG, "setCredentialModel " + mCredentialModel + ", savedInstanceState exist:"
+                    + (savedInstanceState != null));
+        }
     }
 
     /**
-     * Observe ActionLiveData for actions about choosing lock, confirming lock, or finishing
-     * activity
+     * Handle onSaveInstanceState from activity
      */
-    @NonNull
-    public LiveData<Integer> getActionLiveData() {
-        return mActionLiveData;
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        outState.putBundle(KEY_CREDENTIAL_MODEL, mCredentialModel.getBundle());
     }
 
-    @Override
-    public void onCreate(@NonNull LifecycleOwner owner) {
-        checkCredential();
+    @NonNull
+    public LiveData<Boolean> getGenerateChallengeFailLiveData() {
+        return mGenerateChallengeFailLiveData;
     }
 
     /**
      * Check credential status for biometric enrollment.
      */
-    private void checkCredential() {
+    @CredentialAction
+    public int checkCredential() {
         if (isValidCredential()) {
-            return;
+            return CREDENTIAL_VALID;
         }
         final long gkPwHandle = mCredentialModel.getGkPwHandle();
         if (isUnspecifiedPassword()) {
-            mActionLiveData.postValue(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK);
+            return CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK;
         } else if (CredentialModel.isValidGkPwHandle(gkPwHandle)) {
             generateChallenge(gkPwHandle);
+            return CREDENTIAL_IS_GENERATING_CHALLENGE;
         } else {
-            mActionLiveData.postValue(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
+            return CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK;
         }
     }
 
     private void generateChallenge(long gkPwHandle) {
         mChallengeGenerator.setCallback((sensorId, userId, challenge) -> {
-            mCredentialModel.setSensorId(sensorId);
-            mCredentialModel.setChallenge(challenge);
             try {
                 final byte[] newToken = requestGatekeeperHat(gkPwHandle, challenge, userId);
+                mCredentialModel.setSensorId(sensorId);
+                mCredentialModel.setChallenge(challenge);
                 mCredentialModel.setToken(newToken);
             } catch (IllegalStateException e) {
                 Log.e(TAG, "generateChallenge, IllegalStateException", e);
-                mActionLiveData.postValue(CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE);
+                mGenerateChallengeFailLiveData.postValue(true);
                 return;
             }
 
@@ -231,7 +252,7 @@
             // Check credential again
             if (!isValidCredential()) {
                 Log.w(TAG, "generateChallenge, invalid Credential");
-                mActionLiveData.postValue(CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE);
+                mGenerateChallengeFailLiveData.postValue(true);
             }
         });
         mChallengeGenerator.generateChallenge(getUserId());
@@ -282,16 +303,16 @@
         final VerifyCredentialResponse response = mLockPatternUtils
                 .verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId);
         if (!response.isMatched()) {
-            throw new IllegalStateException("Unable to request Gatekeeper HAT");
+            throw new GatekeeperCredentialNotMatchException("Unable to request Gatekeeper HAT");
         }
         return response.getGatekeeperHAT();
     }
 
     /**
-     * Get Credential bundle which will be used to launch next activity.
+     * Get Credential intent extra which will be used to launch next activity.
      */
     @NonNull
-    public Bundle getCredentialBundle() {
+    public Bundle getCredentialIntentExtra() {
         final Bundle retBundle = new Bundle();
         final long gkPwHandle = mCredentialModel.getGkPwHandle();
         if (CredentialModel.isValidGkPwHandle(gkPwHandle)) {
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java
index 252a508..3274eb2 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java
@@ -145,6 +145,13 @@
     }
 
     /**
+     * Clear user's action live data (like clicking Agree, Skip, or Done)
+     */
+    public void clearActionLiveData() {
+        mActionLiveData.setValue(null);
+    }
+
+    /**
      * Get user's action live data (like clicking Agree, Skip, or Done)
      */
     public LiveData<Integer> getActionLiveData() {
diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSettings.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSettings.java
index 7382299..0eca3a6 100644
--- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSettings.java
+++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSettings.java
@@ -167,7 +167,7 @@
 
     @Override
     public int getMetricsCategory() {
-        return 0;
+        return SettingsEnums.FUELGAUGE_BATTERY_SAVER_SCHEDULE;
     }
 
     private void logPowerSaver() {
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java
index d4c00a4..5fd3905 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java
@@ -43,7 +43,6 @@
 import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip;
 import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip;
 
-import java.text.NumberFormat;
 import java.util.List;
 
 /**
@@ -142,29 +141,6 @@
                         .setPositiveButton(R.string.battery_tip_unrestrict_app_dialog_ok, this)
                         .setNegativeButton(R.string.battery_tip_unrestrict_app_dialog_cancel, null)
                         .create();
-            case BatteryTip.TipType.BATTERY_DEFENDER:
-                mMetricsFeatureProvider.action(context,
-                        SettingsEnums.ACTION_TIP_BATTERY_DEFENDER, mMetricsKey);
-                final double chargeLimitLevel = 0.8f;
-                final String percentage =
-                        NumberFormat.getPercentInstance().format(chargeLimitLevel);
-                final String message = context.getString(
-                        R.string.battery_tip_limited_temporarily_dialog_msg, percentage);
-                final boolean isPluggedIn = isPluggedIn();
-                final AlertDialog.Builder dialogBuilder =
-                        new AlertDialog.Builder(context)
-                                .setTitle(R.string.battery_tip_limited_temporarily_title)
-                                .setMessage(message);
-                if (isPluggedIn) {
-                    dialogBuilder
-                            .setPositiveButton(
-                                    R.string.battery_tip_limited_temporarily_dialog_resume_charge,
-                                    this)
-                            .setNegativeButton(R.string.okay, null);
-                } else {
-                    dialogBuilder.setPositiveButton(R.string.okay, null);
-                }
-                return dialogBuilder.create();
             default:
                 throw new IllegalArgumentException("unknown type " + mBatteryTip.getType());
         }
diff --git a/src/com/android/settings/fuelgauge/batterytip/actions/BatteryDefenderAction.java b/src/com/android/settings/fuelgauge/batterytip/actions/BatteryDefenderAction.java
deleted file mode 100644
index 824b6be..0000000
--- a/src/com/android/settings/fuelgauge/batterytip/actions/BatteryDefenderAction.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2020 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.fuelgauge.batterytip.actions;
-
-import android.content.Intent;
-
-import com.android.settings.SettingsActivity;
-import com.android.settings.overlay.FeatureFactory;
-
-/**
- * Action to open the Support Center article
- */
-public class BatteryDefenderAction extends BatteryTipAction {
-    private SettingsActivity mSettingsActivity;
-
-    public BatteryDefenderAction(SettingsActivity settingsActivity) {
-        super(settingsActivity.getApplicationContext());
-        mSettingsActivity = settingsActivity;
-    }
-
-    @Override
-    public void handlePositiveAction(int metricsKey) {
-        final Intent intent = FeatureFactory.getFactory(mContext)
-                .getPowerUsageFeatureProvider(mContext).getResumeChargeIntent();
-        if (intent != null) {
-            mContext.sendBroadcast(intent);
-        }
-    }
-}
diff --git a/src/com/android/settings/notification/RedactionInterstitial.java b/src/com/android/settings/notification/RedactionInterstitial.java
index f243250..d6fdaf8 100644
--- a/src/com/android/settings/notification/RedactionInterstitial.java
+++ b/src/com/android/settings/notification/RedactionInterstitial.java
@@ -189,13 +189,16 @@
         }
 
         private void loadFromSettings() {
+            final boolean showUnRedactedDefault = getContext().getResources().getBoolean(
+                    R.bool.default_allow_sensitive_lockscreen_content);
             final boolean managedProfile = UserManager.get(getContext()).isManagedProfile(mUserId);
             // Hiding all notifications is device-wide setting, managed profiles can only set
             // whether their notifications are show in full or redacted.
             final boolean showNotifications = managedProfile || Settings.Secure.getIntForUser(
                     getContentResolver(), LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mUserId) != 0;
             final boolean showUnredacted = Settings.Secure.getIntForUser(
-                    getContentResolver(), LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, mUserId) != 0;
+                    getContentResolver(), LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+                    showUnRedactedDefault ? 1 : 0, mUserId) != 0;
 
             int checkedButtonId = R.id.hide_all;
             if (showNotifications) {
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSettingsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSettingsTest.java
index 270a625..dc9cac4 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSettingsTest.java
@@ -1,5 +1,6 @@
 package com.android.settings.fuelgauge.batterysaver;
 
+import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -82,6 +83,12 @@
         verifySchedule("key_battery_saver_percentage", expectedPercentage);
     }
 
+    @Test
+    public void getMetricsCategory_returnExpectedResult() {
+        assertThat(mBatterySaverScheduleSettings.getMetricsCategory())
+                .isEqualTo(SettingsEnums.FUELGAUGE_BATTERY_SAVER_SCHEDULE);
+    }
+
     private void setSchedule(int scheduleType, int schedulePercentage) {
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.AUTOMATIC_POWER_SAVE_MODE, scheduleType);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java
index db1159e..c5d66a6 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java
@@ -244,20 +244,4 @@
         assertThat(shadowDialog.getMessage()).isEqualTo(
                 mContext.getText(R.string.battery_tip_dialog_summary_message));
     }
-
-    @Test
-    public void testOnCreateDialog_defenderTip_fireDialog() {
-        mDialogFragment = BatteryTipDialogFragment.newInstance(mDefenderTip, METRICS_KEY);
-
-        FragmentController.setupFragment(mDialogFragment, FragmentActivity.class,
-                0 /* containerViewId */, null /* bundle */);
-
-        final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
-        ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
-
-        assertThat(shadowDialog.getTitle()).isEqualTo(
-                mContext.getString(R.string.battery_tip_limited_temporarily_title));
-        assertThat(shadowDialog.getMessage()).isEqualTo(
-                mContext.getString(R.string.battery_tip_limited_temporarily_dialog_msg, "80%"));
-    }
 }
diff --git a/tests/robotests/src/com/android/settings/notification/RedactionInterstitialTest.java b/tests/robotests/src/com/android/settings/notification/RedactionInterstitialTest.java
index 5c6da49..9d475b8 100644
--- a/tests/robotests/src/com/android/settings/notification/RedactionInterstitialTest.java
+++ b/tests/robotests/src/com/android/settings/notification/RedactionInterstitialTest.java
@@ -21,6 +21,7 @@
 import com.android.settings.R;
 import com.android.settings.RestrictedRadioButton;
 import com.android.settings.notification.RedactionInterstitial.RedactionInterstitialFragment;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
 import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal;
 import com.android.settings.testutils.shadow.ShadowUtils;
 
@@ -38,6 +39,7 @@
 @Config(shadows = {
         ShadowUtils.class,
         ShadowRestrictedLockUtilsInternal.class,
+        SettingsShadowResources.class,
 })
 public class RedactionInterstitialTest {
     private RedactionInterstitial mActivity;
@@ -134,6 +136,28 @@
         assertSelectedButton(R.id.redact_sensitive);
     }
 
+    @Test
+    public void defaultShowSensitiveContent_configDeny() {
+        final ContentResolver resolver = RuntimeEnvironment.application.getContentResolver();
+        Settings.Secure.putIntForUser(resolver,
+                LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, UserHandle.myUserId());
+        setupConfig(false);
+        setupActivity();
+
+        assertSelectedButton(R.id.redact_sensitive);
+    }
+
+    @Test
+    public void defaultShowSensitiveContent_configAllow() {
+        final ContentResolver resolver = RuntimeEnvironment.application.getContentResolver();
+        Settings.Secure.putIntForUser(resolver,
+                LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, UserHandle.myUserId());
+        setupConfig(true);
+        setupActivity();
+
+        assertSelectedButton(R.id.show_all);
+    }
+
     private void setupActivity() {
         mActivity = buildActivity(RedactionInterstitial.class, new Intent()).setup().get();
         mFragment = (RedactionInterstitialFragment)
@@ -142,6 +166,11 @@
         assertThat(mFragment).isNotNull();
     }
 
+    private void setupConfig(boolean allowSensitiveContent) {
+        SettingsShadowResources.overrideResource(
+                R.bool.default_allow_sensitive_lockscreen_content, allowSensitiveContent);
+    }
+
     private void setupSettings(int show, int showUnredacted) {
         final ContentResolver resolver = RuntimeEnvironment.application.getContentResolver();
         Settings.Secure.putIntForUser(resolver,
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppAllServicesPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppAllServicesPreferenceTest.kt
index 9846e3f..bd73d8b 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppAllServicesPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppAllServicesPreferenceTest.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.onRoot
@@ -38,6 +39,8 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settings.R
+import com.android.settingslib.spa.testutils.delay
+import com.android.settingslib.spa.testutils.waitUntilExists
 import com.android.settingslib.spaprivileged.model.app.userHandle
 import com.android.settingslib.spaprivileged.model.app.userId
 import com.google.common.truth.Truth.assertThat
@@ -140,7 +143,7 @@
 
         setContent()
 
-        composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed()
+        composeTestRule.waitUntilExists(hasText(SUMMARY))
     }
 
     @Test
@@ -149,6 +152,7 @@
 
         setContent()
         composeTestRule.onRoot().performClick()
+        composeTestRule.delay()
 
         val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
         verify(context).startActivityAsUser(intentCaptor.capture(), eq(APP.userHandle))
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppLocalePreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppLocalePreferenceTest.kt
index 60c4f79..39524df 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppLocalePreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppLocalePreferenceTest.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.onRoot
@@ -36,6 +37,8 @@
 import com.android.settings.applications.AppLocaleUtil
 import com.android.settings.applications.appinfo.AppLocaleDetails
 import com.android.settings.localepicker.AppLocalePickerActivity
+import com.android.settingslib.spa.testutils.delay
+import com.android.settingslib.spa.testutils.waitUntilExists
 import com.android.settingslib.spaprivileged.model.app.userHandle
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
@@ -103,15 +106,16 @@
 
         composeTestRule.onNodeWithText(context.getString(R.string.app_locale_preference_title))
             .assertIsDisplayed()
-        composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed()
+        composeTestRule.waitUntilExists(hasText(SUMMARY))
     }
 
     @Test
-    fun whenCanDisplayLocalUi_click_startsActivity() {
+    fun whenCanDisplayLocalUi_click_startActivity() {
         doNothing().`when`(context).startActivityAsUser(any(), any())
 
         setContent()
         composeTestRule.onRoot().performClick()
+        composeTestRule.delay()
 
         val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
         verify(context).startActivityAsUser(intentCaptor.capture(), eq(APP.userHandle))
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppSettingsPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppSettingsPreferenceTest.kt
index a1fb367..35811e2 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppSettingsPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppSettingsPreferenceTest.kt
@@ -36,6 +36,7 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settings.R
+import com.android.settingslib.spa.testutils.delay
 import com.android.settingslib.spaprivileged.model.app.userHandle
 import com.android.settingslib.spaprivileged.model.app.userId
 import com.google.common.truth.Truth.assertThat
@@ -129,6 +130,7 @@
 
         setContent()
         composeTestRule.onRoot().performClick()
+        composeTestRule.delay()
 
         val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
         verify(context).startActivityAsUser(intentCaptor.capture(), eq(APP.userHandle))
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InteractAcrossProfilesDetailsPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InteractAcrossProfilesDetailsPreferenceTest.kt
index aeccb07..f4489c6 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InteractAcrossProfilesDetailsPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InteractAcrossProfilesDetailsPreferenceTest.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.onRoot
@@ -34,6 +35,8 @@
 import com.android.settings.R
 import com.android.settings.applications.appinfo.AppInfoDashboardFragment
 import com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesDetails
+import com.android.settingslib.spa.testutils.delay
+import com.android.settingslib.spa.testutils.waitUntilExists
 import com.android.settingslib.spaprivileged.framework.common.crossProfileApps
 import org.junit.After
 import org.junit.Before
@@ -110,7 +113,7 @@
 
         setContent()
 
-        composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed()
+        composeTestRule.waitUntilExists(hasText(SUMMARY))
     }
 
     @Test
@@ -119,6 +122,7 @@
 
         setContent()
         composeTestRule.onRoot().performClick()
+        composeTestRule.delay()
 
         ExtendedMockito.verify {
             AppInfoDashboardFragment.startAppInfoFragment(
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.java
new file mode 100644
index 0000000..1fac8b5
--- /dev/null
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 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.biometrics2.ui.model;
+
+import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE;
+import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_SENSOR_ID;
+import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_CHALLENGE;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.password.ChooseLockSettingsHelper;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Clock;
+import java.util.Arrays;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class CredentialModelTest {
+
+    private final Clock mClock = SystemClock.elapsedRealtimeClock();
+
+    public static Bundle newCredentialModelIntentExtras(int userId, long challenge, int sensorId,
+            @Nullable byte[] token, long gkPwHandle) {
+        final Bundle bundle = new Bundle();
+        bundle.putInt(Intent.EXTRA_USER_ID, userId);
+        bundle.putInt(EXTRA_KEY_SENSOR_ID, sensorId);
+        bundle.putLong(EXTRA_KEY_CHALLENGE, challenge);
+        bundle.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
+        bundle.putLong(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
+        return bundle;
+    }
+
+    public static Bundle newValidTokenCredentialIntentExtras(int userId) {
+        return newCredentialModelIntentExtras(userId, 1L, 1, new byte[] { 0 }, 0L);
+    }
+
+    public static Bundle newInvalidChallengeCredentialIntentExtras(int userId) {
+        return newCredentialModelIntentExtras(userId, INVALID_CHALLENGE, 1, null, 0L);
+    }
+
+    public static Bundle newGkPwHandleCredentialIntentExtras(int userId, long gkPwHandle) {
+        return newCredentialModelIntentExtras(userId, INVALID_CHALLENGE, 1, null, gkPwHandle);
+    }
+
+    private static void checkBundleLongValue(@NonNull Bundle bundle1, @NonNull Bundle bundle2,
+            @NonNull String key) {
+        if (!bundle1.containsKey(key)) {
+            return;
+        }
+        final int value1 = bundle1.getInt(key);
+        final int value2 = bundle2.getInt(key);
+        assertWithMessage("bundle not match, key:" + key + ", value1:" + value1 + ", value2:"
+                + value2).that(value1).isEqualTo(value2);
+    }
+
+    private static void checkBundleIntValue(@NonNull Bundle bundle1, @NonNull Bundle bundle2,
+            @NonNull String key) {
+        if (!bundle1.containsKey(key)) {
+            return;
+        }
+        final long value1 = bundle1.getLong(key);
+        final long value2 = bundle2.getLong(key);
+        assertWithMessage("bundle not match, key:" + key + ", value1:" + value1 + ", value2:"
+                + value2).that(value1).isEqualTo(value2);
+    }
+
+    private static void checkBundleByteArrayValue(@NonNull Bundle bundle1, @NonNull Bundle bundle2,
+            @NonNull String key) {
+        if (!bundle1.containsKey(key)) {
+            return;
+        }
+        final byte[] value1 = bundle1.getByteArray(key);
+        final byte[] value2 = bundle2.getByteArray(key);
+        final String errMsg = "bundle not match, key:" + key + ", value1:" + Arrays.toString(value1)
+                + ", value2:" + Arrays.toString(value2);
+        if (value1 == null) {
+            assertWithMessage(errMsg).that(value2).isNull();
+        } else {
+            assertWithMessage(errMsg).that(value1.length).isEqualTo(value2.length);
+            for (int i = 0; i < value1.length; ++i) {
+                assertWithMessage(errMsg).that(value1[i]).isEqualTo(value2[i]);
+            }
+        }
+    }
+
+    public static void verifySameCredentialModels(@NonNull CredentialModel model1,
+            @NonNull CredentialModel model2) {
+
+        assertThat(model1.getUserId()).isEqualTo(model2.getUserId());
+        assertThat(model1.getSensorId()).isEqualTo(model2.getSensorId());
+        assertThat(model1.getChallenge()).isEqualTo(model2.getChallenge());
+        assertThat(model1.getGkPwHandle()).isEqualTo(model2.getGkPwHandle());
+
+        final byte[] token1 = model1.getToken();
+        final byte[] token2 = model2.getToken();
+        if (token1 == null) {
+            assertThat(token2).isNull();
+        } else {
+            assertThat(token2).isNotNull();
+            assertThat(token1.length).isEqualTo(token2.length);
+            for (int i = 0; i < token1.length; ++i) {
+                assertThat(token1[i]).isEqualTo(token2[i]);
+            }
+        }
+
+        final Bundle bundle1 = model1.getBundle();
+        final Bundle bundle2 = model2.getBundle();
+        final Set<String> keySet1 = bundle1.keySet();
+        assertThat(keySet1.equals(bundle2.keySet())).isTrue();
+        checkBundleIntValue(bundle1, bundle2, Intent.EXTRA_USER_ID);
+        checkBundleIntValue(bundle1, bundle2, EXTRA_KEY_SENSOR_ID);
+        checkBundleLongValue(bundle1, bundle2, EXTRA_KEY_CHALLENGE);
+        checkBundleByteArrayValue(bundle1, bundle2, EXTRA_KEY_CHALLENGE);
+        checkBundleLongValue(bundle1, bundle2, EXTRA_KEY_GK_PW_HANDLE);
+    }
+
+    @Test
+    public void sameValueFromBundle() {
+        final Bundle bundle = newCredentialModelIntentExtras(1234, 6677L, 1,
+                new byte[] { 33, 44, 55 }, 987654321);
+
+        final CredentialModel model1 = new CredentialModel(bundle, mClock);
+        final CredentialModel model2 = new CredentialModel(model1.getBundle(), mClock);
+
+        verifySameCredentialModels(model1, model2);
+    }
+
+    @Test
+    public void sameValueFromBundle_nullToken() {
+        final Bundle bundle = newCredentialModelIntentExtras(22, 33L, 1, null, 21L);
+
+        final CredentialModel model1 = new CredentialModel(bundle, mClock);
+        final CredentialModel model2 = new CredentialModel(model1.getBundle(), mClock);
+
+        verifySameCredentialModels(model1, model2);
+    }
+}
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java
index 7a13875..a11650e 100644
--- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java
@@ -24,12 +24,22 @@
 import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE;
 import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_SENSOR_ID;
 import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_CHALLENGE;
+import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_GK_PW_HANDLE;
 import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_SENSOR_ID;
+import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newCredentialModelIntentExtras;
+import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newGkPwHandleCredentialIntentExtras;
+import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newInvalidChallengeCredentialIntentExtras;
+import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newValidTokenCredentialIntentExtras;
 import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK;
 import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK;
+import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE;
+import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID;
 import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator;
 import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CredentialAction;
 import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.GenerateChallengeCallback;
+import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.KEY_CREDENTIAL_MODEL;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -38,12 +48,11 @@
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.content.Intent;
-import android.os.SystemClock;
+import android.os.Bundle;
 import android.os.UserHandle;
 
 import androidx.activity.result.ActivityResult;
 import androidx.annotation.Nullable;
-import androidx.lifecycle.LifecycleOwner;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
@@ -68,7 +77,6 @@
     @Rule public final MockitoRule mockito = MockitoJUnit.rule();
     @Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();
 
-    @Mock private LifecycleOwner mLifecycleOwner;
     @Mock private LockPatternUtils mLockPatternUtils;
     private TestChallengeGenerator mChallengeGenerator = null;
     private AutoCredentialViewModel mAutoCredentialViewModel;
@@ -82,142 +90,222 @@
                 mChallengeGenerator);
     }
 
-    private CredentialModel newCredentialModel(int userId, long challenge,
-            @Nullable byte[] token, long gkPwHandle) {
-        final Intent intent = new Intent();
-        intent.putExtra(Intent.EXTRA_USER_ID, userId);
-        intent.putExtra(EXTRA_KEY_SENSOR_ID, 1);
-        intent.putExtra(EXTRA_KEY_CHALLENGE, challenge);
-        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
-        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
-        return new CredentialModel(intent, SystemClock.elapsedRealtimeClock());
-    }
-
-    private CredentialModel newValidTokenCredentialModel(int userId) {
-        return newCredentialModel(userId, 1L, new byte[] { 0 }, 0L);
-    }
-
-    private CredentialModel newInvalidChallengeCredentialModel(int userId) {
-        return newCredentialModel(userId, INVALID_CHALLENGE, null, 0L);
-    }
-
-    private CredentialModel newGkPwHandleCredentialModel(int userId, long gkPwHandle) {
-        return newCredentialModel(userId, INVALID_CHALLENGE, null, gkPwHandle);
-    }
-
-    private void verifyNothingHappen() {
-        assertThat(mAutoCredentialViewModel.getActionLiveData().getValue()).isNull();
-    }
-
-    private void verifyOnlyActionLiveData(@CredentialAction int action) {
-        final Integer value = mAutoCredentialViewModel.getActionLiveData().getValue();
-        assertThat(value).isEqualTo(action);
-    }
-
-    private void setupGenerateTokenFlow(long gkPwHandle, int userId, int newSensorId,
-            long newChallenge) {
+    private void setupGenerateChallenge(int userId, int newSensorId, long newChallenge) {
         when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
                 PASSWORD_QUALITY_SOMETHING);
         mChallengeGenerator.mUserId = userId;
         mChallengeGenerator.mSensorId = newSensorId;
         mChallengeGenerator.mChallenge = newChallenge;
-        when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
-                .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
     }
 
     @Test
-    public void checkCredential_validCredentialCase() {
+    public void testGetCredentialIntentExtra_sameResultFromSavedInstanceOrIntent() {
+        final Bundle extras = newCredentialModelIntentExtras(12, 33, 1, new byte[] { 2, 3 }, 3L);
+
+        AutoCredentialViewModel autoCredentialViewModel2 = new AutoCredentialViewModel(
+                ApplicationProvider.getApplicationContext(),
+                mLockPatternUtils,
+                mChallengeGenerator);
+
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(extras));
+        final Bundle savedInstance = new Bundle();
+        savedInstance.putBundle(KEY_CREDENTIAL_MODEL, extras);
+        autoCredentialViewModel2.setCredentialModel(savedInstance, new Intent());
+
+        final Bundle bundle1 = mAutoCredentialViewModel.getCredentialIntentExtra();
+        final Bundle bundle2 = autoCredentialViewModel2.getCredentialIntentExtra();
+        assertThat(bundle1.getLong(EXTRA_KEY_GK_PW_HANDLE))
+                .isEqualTo(bundle2.getLong(EXTRA_KEY_GK_PW_HANDLE));
+        assertThat(bundle1.getLong(Intent.EXTRA_USER_ID))
+                .isEqualTo(bundle2.getLong(Intent.EXTRA_USER_ID));
+        assertThat(bundle1.getLong(EXTRA_KEY_CHALLENGE))
+                .isEqualTo(bundle2.getLong(EXTRA_KEY_CHALLENGE));
+        assertThat(bundle1.getInt(EXTRA_KEY_SENSOR_ID))
+                .isEqualTo(bundle2.getInt(EXTRA_KEY_SENSOR_ID));
+        final byte[] token1 = bundle1.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN);
+        final byte[] token2 = bundle2.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN);
+        assertThat(token1).isNotNull();
+        assertThat(token2).isNotNull();
+        assertThat(token1.length).isEqualTo(token2.length);
+        for (int i = 0; i < token2.length; ++i) {
+            assertThat(token1[i]).isEqualTo(token2[i]);
+        }
+    }
+
+    @Test
+    public void testGetCredentialIntentExtra_sameResultFromSavedInstanceOrIntent_invalidValues() {
+        final Bundle extras = newCredentialModelIntentExtras(UserHandle.USER_NULL,
+                INVALID_CHALLENGE, INVALID_SENSOR_ID, null, INVALID_GK_PW_HANDLE);
+
+        AutoCredentialViewModel autoCredentialViewModel2 = new AutoCredentialViewModel(
+                ApplicationProvider.getApplicationContext(),
+                mLockPatternUtils,
+                mChallengeGenerator);
+
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(extras));
+        final Bundle savedInstance = new Bundle();
+        savedInstance.putBundle(KEY_CREDENTIAL_MODEL, extras);
+        autoCredentialViewModel2.setCredentialModel(savedInstance, new Intent());
+
+        final Bundle bundle1 = mAutoCredentialViewModel.getCredentialIntentExtra();
+        final Bundle bundle2 = autoCredentialViewModel2.getCredentialIntentExtra();
+        assertThat(bundle1.containsKey(EXTRA_KEY_GK_PW_HANDLE)).isFalse();
+        assertThat(bundle2.containsKey(EXTRA_KEY_GK_PW_HANDLE)).isFalse();
+        assertThat(bundle1.containsKey(EXTRA_KEY_CHALLENGE_TOKEN)).isFalse();
+        assertThat(bundle2.containsKey(EXTRA_KEY_CHALLENGE_TOKEN)).isFalse();
+        assertThat(bundle1.containsKey(EXTRA_KEY_SENSOR_ID)).isTrue();
+        assertThat(bundle2.containsKey(EXTRA_KEY_SENSOR_ID)).isTrue();
+        assertThat(bundle1.containsKey(Intent.EXTRA_USER_ID)).isFalse();
+        assertThat(bundle2.containsKey(Intent.EXTRA_USER_ID)).isFalse();
+    }
+
+    @Test
+    public void testCheckCredential_validCredentialCase() {
         final int userId = 99;
-        mAutoCredentialViewModel.setCredentialModel(newValidTokenCredentialModel(userId));
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newValidTokenCredentialIntentExtras(userId)));
         when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
                 PASSWORD_QUALITY_SOMETHING);
 
         // Run credential check
-        mAutoCredentialViewModel.onCreate(mLifecycleOwner);
+        @CredentialAction final int action = mAutoCredentialViewModel.checkCredential();
 
-        verifyNothingHappen();
+        assertThat(action).isEqualTo(CREDENTIAL_VALID);
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull();
     }
 
     @Test
-    public void checkCredential_needToChooseLock() {
+    public void testCheckCredential_needToChooseLock() {
         final int userId = 100;
-        mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId));
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId)));
         when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
                 PASSWORD_QUALITY_UNSPECIFIED);
 
         // Run credential check
-        mAutoCredentialViewModel.onCreate(mLifecycleOwner);
+        @CredentialAction final int action = mAutoCredentialViewModel.checkCredential();
 
-        verifyOnlyActionLiveData(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK);
+        assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK);
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull();
     }
 
     @Test
-    public void checkCredential_needToConfirmLockFoSomething() {
+    public void testCheckCredential_needToConfirmLockFoSomething() {
         final int userId = 101;
-        mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId));
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId)));
         when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
                 PASSWORD_QUALITY_SOMETHING);
 
         // Run credential check
-        mAutoCredentialViewModel.onCreate(mLifecycleOwner);
+        @CredentialAction final int action = mAutoCredentialViewModel.checkCredential();
 
-        verifyOnlyActionLiveData(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
+        assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull();
     }
 
     @Test
-    public void checkCredential_needToConfirmLockForNumeric() {
+    public void testCheckCredential_needToConfirmLockForNumeric() {
         final int userId = 102;
-        mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId));
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId)));
         when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
                 PASSWORD_QUALITY_NUMERIC);
 
         // Run credential check
-        mAutoCredentialViewModel.onCreate(mLifecycleOwner);
+        @CredentialAction final int action = mAutoCredentialViewModel.checkCredential();
 
-        verifyOnlyActionLiveData(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
+        assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull();
     }
 
     @Test
-    public void checkCredential_needToConfirmLockForAlphabetic() {
+    public void testCheckCredential_needToConfirmLockForAlphabetic() {
         final int userId = 103;
-        mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId));
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId)));
         when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
                 PASSWORD_QUALITY_ALPHABETIC);
 
         // Run credential check
-        mAutoCredentialViewModel.onCreate(mLifecycleOwner);
+        @CredentialAction final int action = mAutoCredentialViewModel.checkCredential();
 
-        verifyOnlyActionLiveData(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
+        assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull();
     }
 
     @Test
-    public void checkCredential_generateChallenge() {
+    public void testCheckCredential_generateChallenge() {
         final int userId = 104;
         final long gkPwHandle = 1111L;
-        final CredentialModel credentialModel = newGkPwHandleCredentialModel(userId, gkPwHandle);
-        mAutoCredentialViewModel.setCredentialModel(credentialModel);
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle)));
         when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
                 PASSWORD_QUALITY_SOMETHING);
 
         final int newSensorId = 10;
         final long newChallenge = 20L;
-        setupGenerateTokenFlow(gkPwHandle, userId, newSensorId, newChallenge);
+        setupGenerateChallenge(userId, newSensorId, newChallenge);
+        when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
+                .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
 
         // Run credential check
-        mAutoCredentialViewModel.onCreate(mLifecycleOwner);
+        @CredentialAction final int action = mAutoCredentialViewModel.checkCredential();
 
-        assertThat(mAutoCredentialViewModel.getActionLiveData().getValue()).isNull();
-        assertThat(credentialModel.getSensorId()).isEqualTo(newSensorId);
-        assertThat(credentialModel.getChallenge()).isEqualTo(newChallenge);
-        assertThat(CredentialModel.isValidToken(credentialModel.getToken())).isTrue();
-        assertThat(CredentialModel.isValidGkPwHandle(credentialModel.getGkPwHandle())).isFalse();
+        assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE);
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull();
+        final Bundle extras = mAutoCredentialViewModel.getCredentialIntentExtra();
+        assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId);
+        assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge);
+        assertThat(CredentialModel.isValidToken(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)))
+                .isTrue();
+        assertThat(CredentialModel.isValidGkPwHandle(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)))
+                .isFalse();
         assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
     }
 
     @Test
-    public void testGetUserId() {
+    public void testCheckCredential_generateChallengeFail() {
+        final int userId = 104;
+        final long gkPwHandle = 1111L;
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle)));
+        when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
+                PASSWORD_QUALITY_SOMETHING);
+
+        final int newSensorId = 10;
+        final long newChallenge = 20L;
+        setupGenerateChallenge(userId, newSensorId, newChallenge);
+        when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
+                .thenReturn(newBadCredential(0));
+
+        // Run credential check
+        @CredentialAction final int action = mAutoCredentialViewModel.checkCredential();
+
+        assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE);
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isTrue();
+        assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
+    }
+
+    @Test
+    public void testGetUserId_fromIntent() {
         final int userId = 106;
-        mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId));
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId)));
+
+        // Get userId
+        assertThat(mAutoCredentialViewModel.getUserId()).isEqualTo(userId);
+    }
+
+    @Test
+    public void testGetUserId_fromSavedInstance() {
+        final int userId = 106;
+        final Bundle savedInstance = new Bundle();
+        savedInstance.putBundle(KEY_CREDENTIAL_MODEL,
+                newInvalidChallengeCredentialIntentExtras(userId));
+        mAutoCredentialViewModel.setCredentialModel(savedInstance, new Intent());
 
         // Get userId
         assertThat(mAutoCredentialViewModel.getUserId()).isEqualTo(userId);
@@ -227,8 +315,8 @@
     public void testCheckNewCredentialFromActivityResult_invalidChooseLock() {
         final int userId = 107;
         final long gkPwHandle = 3333L;
-        mAutoCredentialViewModel.setCredentialModel(
-                newGkPwHandleCredentialModel(userId, gkPwHandle));
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle)));
         final Intent intent = new Intent();
         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
 
@@ -237,15 +325,15 @@
                 new ActivityResult(ChooseLockPattern.RESULT_FINISHED + 1, intent));
 
         assertThat(ret).isFalse();
-        verifyNothingHappen();
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull();
     }
 
     @Test
     public void testCheckNewCredentialFromActivityResult_invalidConfirmLock() {
         final int userId = 107;
         final long gkPwHandle = 3333L;
-        mAutoCredentialViewModel.setCredentialModel(
-                newGkPwHandleCredentialModel(userId, gkPwHandle));
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle)));
         final Intent intent = new Intent();
         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
 
@@ -254,62 +342,68 @@
                 new ActivityResult(Activity.RESULT_OK + 1, intent));
 
         assertThat(ret).isFalse();
-        verifyNothingHappen();
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull();
     }
 
     @Test
     public void testCheckNewCredentialFromActivityResult_nullDataChooseLock() {
         final int userId = 108;
         final long gkPwHandle = 4444L;
-        mAutoCredentialViewModel.setCredentialModel(
-                newGkPwHandleCredentialModel(userId, gkPwHandle));
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle)));
 
         // run checkNewCredentialFromActivityResult()
         final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(true,
                 new ActivityResult(ChooseLockPattern.RESULT_FINISHED, null));
 
         assertThat(ret).isFalse();
-        verifyNothingHappen();
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull();
     }
 
     @Test
     public void testCheckNewCredentialFromActivityResult_nullDataConfirmLock() {
         final int userId = 109;
-        mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId));
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId)));
 
         // run checkNewCredentialFromActivityResult()
         final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(false,
                 new ActivityResult(Activity.RESULT_OK, null));
 
         assertThat(ret).isFalse();
-        verifyNothingHappen();
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull();
     }
 
     @Test
     public void testCheckNewCredentialFromActivityResult_validChooseLock() {
         final int userId = 108;
-        final CredentialModel credentialModel = newInvalidChallengeCredentialModel(userId);
-        mAutoCredentialViewModel.setCredentialModel(credentialModel);
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId)));
         when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
                 PASSWORD_QUALITY_SOMETHING);
 
         final long gkPwHandle = 6666L;
         final int newSensorId = 50;
         final long newChallenge = 60L;
-        setupGenerateTokenFlow(gkPwHandle, userId, newSensorId, newChallenge);
-        final Intent intent = new Intent();
-        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
+        setupGenerateChallenge(userId, newSensorId, newChallenge);
+        when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
+                .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
 
         // Run checkNewCredentialFromActivityResult()
+        final Intent intent = new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
+                gkPwHandle);
         final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(true,
                 new ActivityResult(ChooseLockPattern.RESULT_FINISHED, intent));
 
         assertThat(ret).isTrue();
-        assertThat(mAutoCredentialViewModel.getActionLiveData().getValue()).isNull();
-        assertThat(credentialModel.getSensorId()).isEqualTo(newSensorId);
-        assertThat(credentialModel.getChallenge()).isEqualTo(newChallenge);
-        assertThat(CredentialModel.isValidToken(credentialModel.getToken())).isTrue();
-        assertThat(CredentialModel.isValidGkPwHandle(credentialModel.getGkPwHandle())).isFalse();
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull();
+        final Bundle extras = mAutoCredentialViewModel.getCredentialIntentExtra();
+        assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId);
+        assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge);
+        assertThat(CredentialModel.isValidToken(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)))
+                .isTrue();
+        assertThat(CredentialModel.isValidGkPwHandle(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)))
+                .isFalse();
         assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
     }
 
@@ -317,28 +411,33 @@
     @Test
     public void testCheckNewCredentialFromActivityResult_validConfirmLock() {
         final int userId = 109;
-        final CredentialModel credentialModel = newInvalidChallengeCredentialModel(userId);
-        mAutoCredentialViewModel.setCredentialModel(credentialModel);
+        mAutoCredentialViewModel.setCredentialModel(null,
+                new Intent().putExtras(newInvalidChallengeCredentialIntentExtras(userId)));
         when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
                 PASSWORD_QUALITY_SOMETHING);
 
         final long gkPwHandle = 5555L;
         final int newSensorId = 80;
         final long newChallenge = 90L;
-        setupGenerateTokenFlow(gkPwHandle, userId, newSensorId, newChallenge);
-        final Intent intent = new Intent();
-        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
+        setupGenerateChallenge(userId, newSensorId, newChallenge);
+        when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
+                .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
 
         // Run checkNewCredentialFromActivityResult()
+        final Intent intent = new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
+                gkPwHandle);
         final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(false,
                 new ActivityResult(Activity.RESULT_OK, intent));
 
         assertThat(ret).isTrue();
-        assertThat(mAutoCredentialViewModel.getActionLiveData().getValue()).isNull();
-        assertThat(credentialModel.getSensorId()).isEqualTo(newSensorId);
-        assertThat(credentialModel.getChallenge()).isEqualTo(newChallenge);
-        assertThat(CredentialModel.isValidToken(credentialModel.getToken())).isTrue();
-        assertThat(CredentialModel.isValidGkPwHandle(credentialModel.getGkPwHandle())).isFalse();
+        assertThat(mAutoCredentialViewModel.getGenerateChallengeFailLiveData().getValue()).isNull();
+        final Bundle extras = mAutoCredentialViewModel.getCredentialIntentExtra();
+        assertThat(extras.getInt(EXTRA_KEY_SENSOR_ID)).isEqualTo(newSensorId);
+        assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge);
+        assertThat(CredentialModel.isValidToken(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)))
+                .isTrue();
+        assertThat(CredentialModel.isValidGkPwHandle(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)))
+                .isFalse();
         assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
     }
 
@@ -377,4 +476,12 @@
                 .setGatekeeperHAT(hat)
                 .build();
     }
+
+    private VerifyCredentialResponse newBadCredential(int timeout) {
+        if (timeout > 0) {
+            return VerifyCredentialResponse.fromTimeout(timeout);
+        } else {
+            return VerifyCredentialResponse.fromError();
+        }
+    }
 }
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java
index 5069ea1..921f182 100644
--- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java
@@ -46,6 +46,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MutableLiveData;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
@@ -93,6 +94,18 @@
     }
 
     @Test
+    public void testClearActionLiveData() {
+        final MutableLiveData<Integer> actionLiveData =
+                (MutableLiveData<Integer>) mViewModel.getActionLiveData();
+        actionLiveData.postValue(1);
+        assertThat(actionLiveData.getValue()).isEqualTo(1);
+
+        mViewModel.clearActionLiveData();
+
+        assertThat(actionLiveData.getValue()).isNull();
+    }
+
+    @Test
     public void testGetEnrollmentRequest() {
         final EnrollmentRequest request = newAllFalseRequest(mApplication);