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
This commit is contained in:
Milton Wu
2023-03-31 19:49:46 +08:00
parent 2c427629d8
commit 52a46d0a85
6 changed files with 130 additions and 12 deletions

View File

@@ -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<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;
}

View File

@@ -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<ActivityResultLauncher<Intent>> 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<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() {

View File

@@ -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<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 @@ 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<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)

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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<Intent> 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