[Biometric Onboarding & Edu] Update fingerprint settings page

- Added a feature provider for fingerprint settings page in
  FingerprintFeatureProvider for customization
- When no fingerprint enrolled, disabled the settings buttons instead of
  hiding them.
- Update new UX style for add fingerprint button

Bug: 370940762
Test: manual - 1. Enroll a fingerprint
               2. Go Fingerprint Settings page and remove fingerprint
	       3. Enroll fingerprint again
Test: atest FingerprintSettingsFragmentTest
Flag: com.android.settings.flags.biometrics_onboarding_education

Change-Id: Ibe47bb241c4b20e8e0c5b4a9172aef90bf3727ea
This commit is contained in:
Shawn Lin
2025-01-24 07:08:46 +00:00
parent d6e99d3783
commit e2051d50e3
8 changed files with 177 additions and 20 deletions

View File

@@ -955,6 +955,8 @@
<string name="security_settings_fingerprint">Fingerprint</string>
<!-- Title shown for a category shown for fingerprint settings page. [CHAR LIMIT=22] -->
<string name="security_settings_fingerprint_title">Fingerprints</string>
<!-- Description shown for fingerprint settings page. [CHAR LIMIT=22] -->
<string name="security_settings_fingerprint_description"></string>
<!-- Fingerprint category title - fingerprint options for unlocking the device. [CHAR LIMIT=60] -->
<string name="security_settings_category_use_fingerprint">Use fingerprint to</string>
<!-- Title shown for menu item that launches fingerprint settings or enrollment [CHAR LIMIT=22] -->

View File

@@ -19,6 +19,11 @@
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/security_settings_fingerprint_preference_title">
<com.android.settingslib.widget.TopIntroPreference
android:key="security_settings_fingerprint_description"
settings:searchable="false"
settings:isPreferenceVisible="false" />
<PreferenceCategory
android:key="biometric_settings_use_fingerprint_to"
android:title="@string/security_settings_category_use_fingerprint"

View File

@@ -71,4 +71,13 @@ public interface FingerprintFeatureProvider {
) {
return new FingerprintExtPreferencesProvider();
}
/**
* Gets the feature provider for FingerprintSettings page
* @return the provider
*/
@NonNull
default FingerprintSettingsFeatureProvider getFingerprintSettingsFeatureProvider() {
return FingerprintSettingsFeatureProvider.getInstance();
}
}

View File

@@ -28,6 +28,8 @@ import static com.android.settings.Utils.isPrivateProfile;
import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
import android.annotation.SuppressLint;
import android.app.Activity;
@@ -37,6 +39,7 @@ import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.ResourceId;
import android.graphics.drawable.Drawable;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
@@ -276,6 +279,8 @@ public class FingerprintSettings extends SubSettings {
"security_settings_fingerprint_unlock_category";
private static final String KEY_FINGERPRINT_UNLOCK_FOOTER =
"security_settings_fingerprint_footer";
private static final String KEY_FINGERPRINT_DESCRIPTION =
"security_settings_fingerprint_description";
private static final String KEY_BIOMETRICS_AUTHENTICATION_REQUESTED =
"biometrics_authentication_requested";
private static final String KEY_BIOMETRICS_USE_FINGERPRINT_TO_CATEGORY =
@@ -648,13 +653,16 @@ public class FingerprintSettings extends SubSettings {
mFooterColumns.add(column2);
} else {
final FooterColumn column = new FooterColumn();
final FingerprintSettingsFeatureProvider featureProvider =
FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
.getFingerprintSettingsFeatureProvider();
column.mTitle = getString(isPrivateProfile()
? R.string.private_space_fingerprint_enroll_introduction_message
: R.string.security_settings_fingerprint_enroll_introduction_v3_message,
DeviceHelper.getDeviceName(getActivity()));
column.mLearnMoreClickListener = learnMoreClickListener;
column.mLearnMoreOverrideText = getText(
R.string.security_settings_fingerprint_settings_footer_learn_more);
featureProvider.getSettingPageFooterLearnMoreDescription());
mFooterColumns.add(column);
}
}
@@ -735,6 +743,14 @@ public class FingerprintSettings extends SubSettings {
scrollToPreference(fpPrefKey);
addFingerprintUnlockCategory();
}
final int descriptionRes = FeatureFactory.getFeatureFactory()
.getFingerprintFeatureProvider().getFingerprintSettingsFeatureProvider()
.getSettingPageDescription();
if (ResourceId.isValid(descriptionRes)) {
final Preference preference = findPreference(KEY_FINGERPRINT_DESCRIPTION);
preference.setTitle(descriptionRes);
preference.setVisible(true);
}
createFooterPreference(root);
}
@@ -748,8 +764,7 @@ public class FingerprintSettings extends SubSettings {
R.string.security_settings_fingerprint_title));
}
String keyToReturn = mIsExpressiveThemeStyle
? KEY_FINGERPRINT_ADD_EXPRESSIVE : KEY_FINGERPRINT_ADD;
String keyToReturn = getAddFingerprintPreferenceKey();
final List<Fingerprint> items = mFingerprintManager.getEnrolledFingerprints(mUserId);
final int fingerprintCount = items.size();
for (int i = 0; i < fingerprintCount; i++) {
@@ -785,8 +800,7 @@ public class FingerprintSettings extends SubSettings {
mFingerprintsEnrolledCategory.addPreference(pref);
pref.setOnPreferenceChangeListener(this);
}
mAddFingerprintPreference = findPreference(mIsExpressiveThemeStyle
? KEY_FINGERPRINT_ADD_EXPRESSIVE : KEY_FINGERPRINT_ADD);
mAddFingerprintPreference = findPreference(getAddFingerprintPreferenceKey());
setupAddFingerprintPreference();
return keyToReturn;
}
@@ -839,18 +853,31 @@ public class FingerprintSettings extends SubSettings {
}
private void updateFingerprintUnlockCategoryVisibility() {
final boolean fingerprintUnlockCategoryAvailable =
mFingerprintUnlockCategoryPreferenceController.isAvailable();
if (mFingerprintUnlockCategory.isVisible() != fingerprintUnlockCategoryAvailable) {
mFingerprintUnlockCategory.setVisible(fingerprintUnlockCategoryAvailable);
}
final int categoryStatus =
mFingerprintUnlockCategoryPreferenceController.getAvailabilityStatus();
updatePreferenceVisibility(categoryStatus, mFingerprintUnlockCategory);
if (mRequireScreenOnToAuthPreferenceController != null) {
mRequireScreenOnToAuthPreference.setVisible(
mRequireScreenOnToAuthPreferenceController.isAvailable());
final int status =
mRequireScreenOnToAuthPreferenceController.getAvailabilityStatus();
updatePreferenceVisibility(status, mRequireScreenOnToAuthPreference);
}
if (mScreenOffUnlockUdfpsPreferenceController != null) {
mScreenOffUnlockUdfpsPreference.setVisible(
mScreenOffUnlockUdfpsPreferenceController.isAvailable());
final int status =
mScreenOffUnlockUdfpsPreferenceController.getAvailabilityStatus();
updatePreferenceVisibility(status, mScreenOffUnlockUdfpsPreference);
}
}
private void updatePreferenceVisibility(int availabilityStatus, Preference preference) {
if (availabilityStatus == AVAILABLE) {
preference.setVisible(true);
preference.setEnabled(true);
} else if (availabilityStatus == CONDITIONALLY_UNAVAILABLE) {
preference.setVisible(true);
preference.setEnabled(false);
} else {
preference.setVisible(false);
}
}
@@ -898,8 +925,29 @@ public class FingerprintSettings extends SubSettings {
.setOnPreferenceChangeListener(fingerprintAppController);
}
private void updateUseFingerprintToEnableStatus() {
final PreferenceCategory category =
findPreference(KEY_BIOMETRICS_USE_FINGERPRINT_TO_CATEGORY);
if (!category.isVisible()) {
return;
}
final boolean hasFingerprintEnrolled =
mFingerprintManager.getEnrolledFingerprints(mUserId).size() > 0;
final FingerprintSettingsKeyguardUnlockPreferenceController fpUnlockController =
use(FingerprintSettingsKeyguardUnlockPreferenceController.class);
findPreference(fpUnlockController.getPreferenceKey())
.setEnabled(hasFingerprintEnrolled);
final FingerprintSettingsAppsPreferenceController fingerprintAppController =
use(FingerprintSettingsAppsPreferenceController.class);
findPreference(fingerprintAppController.getPreferenceKey())
.setEnabled(hasFingerprintEnrolled);
}
private void updatePreferencesAfterFingerprintRemoved() {
updateAddPreference();
updateUseFingerprintToEnableStatus();
if (isSfps() || (screenOffUnlockUdfps() && isScreenOffUnlcokSupported())) {
updateFingerprintUnlockCategoryVisibility();
}
@@ -911,8 +959,7 @@ public class FingerprintSettings extends SubSettings {
return; // Activity went away
}
mAddFingerprintPreference = findPreference(
mIsExpressiveThemeStyle ? KEY_FINGERPRINT_ADD_EXPRESSIVE : KEY_FINGERPRINT_ADD);
mAddFingerprintPreference = findPreference(getAddFingerprintPreferenceKey());
if (mAddFingerprintPreference == null) {
return; // b/275519315 Skip if updateAddPreference() invoke before addPreference()
@@ -945,11 +992,11 @@ public class FingerprintSettings extends SubSettings {
findPreference(KEY_FINGERPRINT_ADD_EXPRESSIVE);
if (nonExpressiveBtnPreference != null) {
nonExpressiveBtnPreference.setVisible(!mIsExpressiveThemeStyle);
nonExpressiveBtnPreference.setVisible(!shouldShowExpressiveAddFingerprintPref());
}
if (expressiveBtnPreference != null) {
expressiveBtnPreference.setVisible(mIsExpressiveThemeStyle);
expressiveBtnPreference.setVisible(shouldShowExpressiveAddFingerprintPref());
}
}
@@ -1053,7 +1100,7 @@ public class FingerprintSettings extends SubSettings {
@Override
public boolean onPreferenceTreeClick(Preference pref) {
final String key = pref.getKey();
if (!mIsExpressiveThemeStyle && KEY_FINGERPRINT_ADD.equals(key)) {
if (KEY_FINGERPRINT_ADD.equals(key)) {
mIsEnrolling = true;
Intent intent = new Intent();
intent.setClassName(SETTINGS_PACKAGE_NAME,
@@ -1451,6 +1498,16 @@ public class FingerprintSettings extends SubSettings {
return Utils.isPrivateProfile(mUserId, getContext());
}
private String getAddFingerprintPreferenceKey() {
return shouldShowExpressiveAddFingerprintPref()
? KEY_FINGERPRINT_ADD_EXPRESSIVE : KEY_FINGERPRINT_ADD;
}
private boolean shouldShowExpressiveAddFingerprintPref() {
return Flags.biometricsOnboardingEducation() && mIsExpressiveThemeStyle
&& mFingerprintManager.hasEnrolledTemplates(mUserId);
}
public static class DeleteFingerprintDialog extends InstrumentedDialogFragment
implements DialogInterface.OnClickListener {

View File

@@ -23,6 +23,7 @@ import android.hardware.fingerprint.FingerprintManager;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.Utils;
import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
@@ -53,6 +54,20 @@ public class FingerprintSettingsAppsPreferenceController
isChecked ? ON : OFF, getUserId());
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if (!FingerprintSettings.isFingerprintHardwareDetected(mContext)) {
preference.setEnabled(false);
} else if (!mFingerprintManager.hasEnrolledTemplates(getUserId())) {
preference.setEnabled(false);
} else if (getRestrictingAdmin() != null) {
preference.setEnabled(false);
} else {
preference.setEnabled(true);
}
}
@Override
public int getAvailabilityStatus() {
final ActiveUnlockStatusUtils activeUnlockStatusUtils =

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2025 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.biometrics.fingerprint
import com.android.settings.R
/**
* Provide features for FingerprintSettings page.
*/
open class FingerprintSettingsFeatureProvider {
/**
* Get the description shown in the FingerprintSetting page.
*/
open fun getSettingPageDescription(): Int {
return 0
}
/**
* Get the learn more description shown in the footer of the FingerprintSetting page.
*/
open fun getSettingPageFooterLearnMoreDescription(): Int {
return R.string.security_settings_fingerprint_settings_footer_learn_more
}
companion object {
@JvmStatic
val instance = FingerprintSettingsFeatureProvider()
}
}

View File

@@ -19,9 +19,11 @@ package com.android.settings.biometrics.fingerprint;
import static android.provider.Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.Utils;
import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
@@ -33,9 +35,12 @@ public class FingerprintSettingsKeyguardUnlockPreferenceController
private static final int OFF = 0;
private static final int DEFAULT = ON;
private FingerprintManager mFingerprintManager;
public FingerprintSettingsKeyguardUnlockPreferenceController(
@NonNull Context context, @NonNull String key) {
super(context, key);
mFingerprintManager = Utils.getFingerprintManagerOrNull(context);
}
@Override
@@ -50,6 +55,20 @@ public class FingerprintSettingsKeyguardUnlockPreferenceController
FINGERPRINT_KEYGUARD_ENABLED, isChecked ? ON : OFF, getUserId());
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if (!FingerprintSettings.isFingerprintHardwareDetected(mContext)) {
preference.setEnabled(false);
} else if (!mFingerprintManager.hasEnrolledTemplates(getUserId())) {
preference.setEnabled(false);
} else if (getRestrictingAdmin() != null) {
preference.setEnabled(false);
} else {
preference.setEnabled(true);
}
}
@Override
public int getAvailabilityStatus() {
final ActiveUnlockStatusUtils activeUnlockStatusUtils =

View File

@@ -135,16 +135,20 @@ public class FingerprintSettingsFragmentTest {
@Mock
private Vibrator mVibrator;
private FingerprintSettingsFeatureProvider mFingerprintSettingsFeatureProvider;
private FakeFeatureFactory mFakeFeatureFactory;
private FingerprintAuthenticateSidecar mFingerprintAuthenticateSidecar;
private FingerprintRemoveSidecar mFingerprintRemoveSidecar;
@Before
public void setUp() {
ShadowUtils.setFingerprintManager(mFingerprintManager);
FakeFeatureFactory.setupForTest();
mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
mContext = spy(ApplicationProvider.getApplicationContext());
mFragment = spy(new FingerprintSettingsFragment());
mFingerprintSettingsFeatureProvider = new FingerprintSettingsFeatureProvider();
doReturn(mContext).when(mFragment).getContext();
doReturn(mBiometricManager).when(mContext).getSystemService(BiometricManager.class);
doReturn(true).when(mFingerprintManager).isHardwareDetected();
@@ -152,6 +156,9 @@ public class FingerprintSettingsFragmentTest {
when(mBiometricManager.canAuthenticate(PRIMARY_USER_ID,
BiometricManager.Authenticators.IDENTITY_CHECK))
.thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
when(mFakeFeatureFactory.getFingerprintFeatureProvider()
.getFingerprintSettingsFeatureProvider())
.thenReturn(mFingerprintSettingsFeatureProvider);
}
@After