Pass Timeout back to upper biometric preference
When FaceSettings or FingerprintSettings are closed because of onStop(),
this information can't been passed back to previous Preference screen,
CombinedBiometricSettings, because handlePreferenceTreeClick() from
AbstractPreferenceController class only can launchActivity() throguh
preference's Context.
In order to recevice the activity result code from FaceSettings or
FingerprintSettings, add handleBiometricPreferenceTreeClick() method in
BiometricStatusPreferenceController. Then CombinedBiometricSettings uses
this method to show FaceSettings or FingerprintSettings through
launchActivityForResult().
Bug: 263057093
Test: atest BiometricNavigationUtilsTest
Test: Manually open camera through double-click power key on different
pages inside "Face & Fingerprint Unlock"
Change-Id: I99167739766ad5ea5f204b0f0543ba6ad18fac31
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