Merge "Support battery usage chart start index label is the detailed last full charge time" into udc-dev
diff --git a/res/xml/security_settings_face.xml b/res/xml/security_settings_face.xml
index f0d0350..9fc8a1f 100644
--- a/res/xml/security_settings_face.xml
+++ b/res/xml/security_settings_face.xml
@@ -80,5 +80,6 @@
 
     <com.android.settingslib.widget.FooterPreference
         android:key="security_face_footer"
+        settings:searchable="false"
         settings:controller="com.android.settings.biometrics.face.FaceSettingsFooterPreferenceController" />
 </PreferenceScreen>
diff --git a/src/com/android/settings/biometrics/BiometricNavigationUtils.java b/src/com/android/settings/biometrics/BiometricNavigationUtils.java
index 32d3a32..2d0744b 100644
--- a/src/com/android/settings/biometrics/BiometricNavigationUtils.java
+++ b/src/com/android/settings/biometrics/BiometricNavigationUtils.java
@@ -26,6 +26,9 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.annotation.Nullable;
+
 import com.android.internal.app.UnlaunchableAppActivity;
 import com.android.settings.core.SettingsBaseActivity;
 import com.android.settingslib.RestrictedLockUtils;
@@ -49,15 +52,23 @@
      *
      * @param className The class name of Settings screen to launch.
      * @param extras    Extras to put into the launching {@link Intent}.
+     * @param launcher  Launcher to launch activity if non-quiet mode
      * @return true if the Settings screen is launching.
      */
-    public boolean launchBiometricSettings(Context context, String className, Bundle extras) {
+    public boolean launchBiometricSettings(Context context, String className, Bundle extras,
+            @Nullable ActivityResultLauncher<Intent> launcher) {
         final Intent quietModeDialogIntent = getQuietModeDialogIntent(context);
         if (quietModeDialogIntent != null) {
             context.startActivity(quietModeDialogIntent);
             return false;
         }
-        context.startActivity(getSettingsPageIntent(className, extras));
+
+        final Intent settingsPageIntent = getSettingsPageIntent(className, extras);
+        if (launcher != null) {
+            launcher.launch(settingsPageIntent);
+        } else {
+            context.startActivity(settingsPageIntent);
+        }
         return true;
     }
 
diff --git a/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java b/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java
index 76a23a5..2f9ae6b 100644
--- a/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java
+++ b/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java
@@ -17,10 +17,14 @@
 package com.android.settings.biometrics;
 
 import android.content.Context;
+import android.content.Intent;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.preference.Preference;
 
 import com.android.internal.widget.LockPatternUtils;
@@ -29,6 +33,8 @@
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.overlay.FeatureFactory;
 
+import java.lang.ref.WeakReference;
+
 public abstract class BiometricStatusPreferenceController extends BasePreferenceController {
 
     protected final UserManager mUm;
@@ -39,6 +45,8 @@
 
     private final BiometricNavigationUtils mBiometricNavigationUtils;
     private final ActiveUnlockStatusUtils mActiveUnlockStatusUtils;
+    @NonNull private WeakReference<ActivityResultLauncher<Intent>> mLauncherWeakReference =
+            new WeakReference<>(null);
 
     /**
      * @return true if the controller should be shown exclusively.
@@ -118,14 +126,31 @@
         preference.setSummary(getSummaryText());
     }
 
+    /**
+     * Set ActivityResultLauncher that will be used later during handlePreferenceTreeClick()
+     *
+     * @param preference the preference being compared
+     * @param launcher the ActivityResultLauncher
+     * @return {@code true} if matched preference.
+     */
+    public boolean setPreferenceTreeClickLauncher(@NonNull Preference preference,
+            @Nullable ActivityResultLauncher<Intent> launcher) {
+        if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
+            return false;
+        }
+
+        mLauncherWeakReference = new WeakReference<>(launcher);
+        return true;
+    }
+
     @Override
     public boolean handlePreferenceTreeClick(Preference preference) {
         if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
             return super.handlePreferenceTreeClick(preference);
         }
 
-        return mBiometricNavigationUtils.launchBiometricSettings(
-                preference.getContext(), getSettingsClassName(), preference.getExtras());
+        return mBiometricNavigationUtils.launchBiometricSettings(preference.getContext(),
+                getSettingsClassName(), preference.getExtras(), mLauncherWeakReference.get());
     }
 
     protected int getUserId() {
diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
index 7b02b9d..052b8cd 100644
--- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
+++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
@@ -33,6 +33,9 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.activity.result.ActivityResult;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
@@ -42,14 +45,19 @@
 import com.android.settings.R;
 import com.android.settings.Utils;
 import com.android.settings.biometrics.BiometricEnrollBase;
+import com.android.settings.biometrics.BiometricStatusPreferenceController;
 import com.android.settings.biometrics.BiometricUtils;
 import com.android.settings.biometrics.BiometricsSplitScreenDialog;
 import com.android.settings.core.SettingsBaseActivity;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.password.ChooseLockGeneric;
 import com.android.settings.password.ChooseLockSettingsHelper;
+import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.transition.SettingsTransitionHelper;
 
+import java.util.Collection;
+import java.util.List;
+
 /**
  * Base fragment with the confirming credential functionality for combined biometrics settings.
  */
@@ -78,6 +86,18 @@
     @Nullable private String mRetryPreferenceKey = null;
     @Nullable private Bundle mRetryPreferenceExtra = null;
 
+    private final ActivityResultLauncher<Intent> mFaceOrFingerprintPreferenceLauncher =
+            registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
+                    this::onFaceOrFingerprintPreferenceResult);
+
+    private void onFaceOrFingerprintPreferenceResult(@Nullable ActivityResult result) {
+        if (result != null && result.getResultCode() == BiometricEnrollBase.RESULT_TIMEOUT) {
+            // When "Face Unlock" or "Fingerprint Unlock" is closed due to entering onStop(),
+            // "Face & Fingerprint Unlock" shall also close itself and back to "Security" page.
+            finish();
+        }
+    }
+
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
@@ -165,7 +185,7 @@
                     extras.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
                     extras.putInt(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, sensorId);
                     extras.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge);
-                    super.onPreferenceTreeClick(preference);
+                    onFaceOrFingerprintPreferenceTreeClick(preference);
                 } catch (IllegalStateException e) {
                     if (retry) {
                         mRetryPreferenceKey = preference.getKey();
@@ -200,7 +220,7 @@
                     final Bundle extras = preference.getExtras();
                     extras.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
                     extras.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge);
-                    super.onPreferenceTreeClick(preference);
+                    onFaceOrFingerprintPreferenceTreeClick(preference);
                 } catch (IllegalStateException e) {
                     if (retry) {
                         mRetryPreferenceKey = preference.getKey();
@@ -224,6 +244,33 @@
         return BiometricUtils.requestGatekeeperHat(context, gkPwHandle, userId, challenge);
     }
 
+    /**
+     * Handle preference tree click action for "Face Unlock" or "Fingerprint Unlock" with a launcher
+     * because "Face & Fingerprint Unlock" has to close itself when it gets a specific activity
+     * error code.
+     *
+     * @param preference "Face Unlock" or "Fingerprint Unlock" preference.
+     */
+    private void onFaceOrFingerprintPreferenceTreeClick(@NonNull Preference preference) {
+        Collection<List<AbstractPreferenceController>> controllers = getPreferenceControllers();
+        for (List<AbstractPreferenceController> controllerList : controllers) {
+            for (AbstractPreferenceController controller : controllerList) {
+                if (controller instanceof BiometricStatusPreferenceController) {
+                    final BiometricStatusPreferenceController biometricController =
+                            (BiometricStatusPreferenceController) controller;
+                    if (biometricController.setPreferenceTreeClickLauncher(preference,
+                            mFaceOrFingerprintPreferenceLauncher)) {
+                        if (biometricController.handlePreferenceTreeClick(preference)) {
+                            writePreferenceClickMetric(preference);
+                        }
+                        biometricController.setPreferenceTreeClickLauncher(preference, null);
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
     @Override
     public boolean onPreferenceTreeClick(Preference preference) {
         return onRetryPreferenceTreeClick(preference, true)
diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java
index cf2c09b..c300bae 100644
--- a/src/com/android/settings/biometrics/face/FaceSettings.java
+++ b/src/com/android/settings/biometrics/face/FaceSettings.java
@@ -325,6 +325,8 @@
                 mFaceManager.revokeChallenge(mSensorId, mUserId, mChallenge);
                 mToken = null;
             }
+            // Let parent "Face & Fingerprint Unlock" can use this error code to close itself.
+            setResult(RESULT_TIMEOUT);
             finish();
         }
     }
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index 88a05c3..f60cd0c 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -669,6 +669,7 @@
         public void onStop() {
             super.onStop();
             if (!getActivity().isChangingConfigurations() && !mLaunchedConfirm && !mIsEnrolling) {
+                setResult(RESULT_TIMEOUT);
                 getActivity().finish();
             }
         }
@@ -874,6 +875,12 @@
             } else if (requestCode == AUTO_ADD_FIRST_FINGERPRINT_REQUEST) {
                 if (resultCode != RESULT_FINISHED || data == null) {
                     Log.d(TAG, "Add first fingerprint, fail or null data, result:" + resultCode);
+                    if (resultCode == BiometricEnrollBase.RESULT_TIMEOUT) {
+                        // If "Fingerprint Unlock" is closed because of timeout, notify result code
+                        // back because "Face & Fingerprint Unlock" has to close itself for timeout
+                        // case.
+                        setResult(resultCode);
+                    }
                     finish();
                     return;
                 }
diff --git a/tests/unit/src/com/android/settings/biometrics/BiometricNavigationUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/BiometricNavigationUtilsTest.java
index 395f88f..90652f0 100644
--- a/tests/unit/src/com/android/settings/biometrics/BiometricNavigationUtilsTest.java
+++ b/tests/unit/src/com/android/settings/biometrics/BiometricNavigationUtilsTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -33,6 +34,7 @@
 import android.os.UserManager;
 import android.provider.Settings;
 
+import androidx.activity.result.ActivityResultLauncher;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
@@ -55,6 +57,8 @@
 
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private ActivityResultLauncher<Intent> mLauncher;
     private Context mContext;
     private BiometricNavigationUtils mBiometricNavigationUtils;
 
@@ -72,7 +76,7 @@
         when(mUserManager.isQuietModeEnabled(any())).thenReturn(true);
 
         mBiometricNavigationUtils.launchBiometricSettings(mContext, SETTINGS_CLASS_NAME,
-                Bundle.EMPTY);
+                Bundle.EMPTY, null);
 
         assertQuietModeDialogLaunchRequested();
     }
@@ -82,7 +86,17 @@
         when(mUserManager.isQuietModeEnabled(any())).thenReturn(true);
 
         assertThat(mBiometricNavigationUtils.launchBiometricSettings(
-                mContext, SETTINGS_CLASS_NAME, Bundle.EMPTY)).isFalse();
+                mContext, SETTINGS_CLASS_NAME, Bundle.EMPTY, null)).isFalse();
+    }
+
+    @Test
+    public void launchBiometricSettings_quietMode_withLauncher_notThroughLauncher() {
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(true);
+
+        mBiometricNavigationUtils.launchBiometricSettings(mContext, SETTINGS_CLASS_NAME,
+                Bundle.EMPTY, mLauncher);
+
+        verify(mLauncher, never()).launch(any(Intent.class));
     }
 
     @Test
@@ -90,7 +104,7 @@
         when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
 
         mBiometricNavigationUtils.launchBiometricSettings(
-                mContext, SETTINGS_CLASS_NAME, Bundle.EMPTY);
+                mContext, SETTINGS_CLASS_NAME, Bundle.EMPTY, null);
 
         assertSettingsPageLaunchRequested(false /* shouldContainExtras */);
     }
@@ -100,7 +114,7 @@
         when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
 
         assertThat(mBiometricNavigationUtils.launchBiometricSettings(
-                mContext, SETTINGS_CLASS_NAME, Bundle.EMPTY)).isTrue();
+                mContext, SETTINGS_CLASS_NAME, Bundle.EMPTY, null)).isTrue();
     }
 
     @Test
@@ -108,17 +122,29 @@
         when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
 
         final Bundle extras = createNotEmptyExtras();
-        mBiometricNavigationUtils.launchBiometricSettings(mContext, SETTINGS_CLASS_NAME, extras);
+        mBiometricNavigationUtils.launchBiometricSettings(
+                mContext, SETTINGS_CLASS_NAME, extras, null);
 
         assertSettingsPageLaunchRequested(true /* shouldContainExtras */);
     }
 
     @Test
+    public void launchBiometricSettings_noQuietMode_withLauncher_launchesThroughLauncher() {
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
+
+        final Bundle extras = createNotEmptyExtras();
+        mBiometricNavigationUtils.launchBiometricSettings(
+                mContext, SETTINGS_CLASS_NAME, extras, mLauncher);
+
+        verify(mLauncher).launch(any(Intent.class));
+    }
+
+    @Test
     public void launchBiometricSettings_noQuietMode_withExtras_returnsTrue() {
         when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
 
         assertThat(mBiometricNavigationUtils.launchBiometricSettings(
-                mContext, SETTINGS_CLASS_NAME, createNotEmptyExtras())).isTrue();
+                mContext, SETTINGS_CLASS_NAME, createNotEmptyExtras(), null)).isTrue();
     }
 
     @Test