diff --git a/src/com/android/settings/biometrics/BiometricNavigationUtils.java b/src/com/android/settings/biometrics/BiometricNavigationUtils.java index 32d3a328d95..2d0744b5b3f 100644 --- a/src/com/android/settings/biometrics/BiometricNavigationUtils.java +++ b/src/com/android/settings/biometrics/BiometricNavigationUtils.java @@ -26,6 +26,9 @@ import android.os.Bundle; 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 @@ public class BiometricNavigationUtils { * * @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 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 76a23a5f059..2f9ae6b8117 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.biometrics.activeunlock.ActiveUnlockStatusUtils; 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 @@ public abstract class BiometricStatusPreferenceController extends BasePreference private final BiometricNavigationUtils mBiometricNavigationUtils; private final ActiveUnlockStatusUtils mActiveUnlockStatusUtils; + @NonNull private WeakReference> mLauncherWeakReference = + new WeakReference<>(null); /** * @return true if the controller should be shown exclusively. @@ -118,14 +126,31 @@ public abstract class BiometricStatusPreferenceController extends BasePreference 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 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 7b02b9dabe2..052b8cd4787 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.os.UserHandle; 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 androidx.preference.Preference; 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 @@ public abstract class BiometricsSettingsBase extends DashboardFragment { @Nullable private String mRetryPreferenceKey = null; @Nullable private Bundle mRetryPreferenceExtra = null; + private final ActivityResultLauncher 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 @@ public abstract class BiometricsSettingsBase extends DashboardFragment { 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 @@ public abstract class BiometricsSettingsBase extends DashboardFragment { 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 @@ public abstract class BiometricsSettingsBase extends DashboardFragment { 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> controllers = getPreferenceControllers(); + for (List 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 cf2c09bc930..c300bae8ebc 100644 --- a/src/com/android/settings/biometrics/face/FaceSettings.java +++ b/src/com/android/settings/biometrics/face/FaceSettings.java @@ -325,6 +325,8 @@ public class FaceSettings extends DashboardFragment { 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 88a05c3d037..f60cd0cd66b 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -669,6 +669,7 @@ public class FingerprintSettings extends SubSettings { public void onStop() { super.onStop(); if (!getActivity().isChangingConfigurations() && !mLaunchedConfirm && !mIsEnrolling) { + setResult(RESULT_TIMEOUT); getActivity().finish(); } } @@ -874,6 +875,12 @@ public class FingerprintSettings extends SubSettings { } 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 395f88fec2d..90652f0d937 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 com.google.common.truth.Truth.assertThat; 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.UserHandle; 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 @@ public class BiometricNavigationUtilsTest { @Mock private UserManager mUserManager; + @Mock + private ActivityResultLauncher mLauncher; private Context mContext; private BiometricNavigationUtils mBiometricNavigationUtils; @@ -72,7 +76,7 @@ public class BiometricNavigationUtilsTest { when(mUserManager.isQuietModeEnabled(any())).thenReturn(true); mBiometricNavigationUtils.launchBiometricSettings(mContext, SETTINGS_CLASS_NAME, - Bundle.EMPTY); + Bundle.EMPTY, null); assertQuietModeDialogLaunchRequested(); } @@ -82,7 +86,17 @@ public class BiometricNavigationUtilsTest { 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 @@ public class BiometricNavigationUtilsTest { 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 @@ public class BiometricNavigationUtilsTest { 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 @@ public class BiometricNavigationUtilsTest { 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