[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> <string name="security_settings_fingerprint">Fingerprint</string>
<!-- Title shown for a category shown for fingerprint settings page. [CHAR LIMIT=22] --> <!-- Title shown for a category shown for fingerprint settings page. [CHAR LIMIT=22] -->
<string name="security_settings_fingerprint_title">Fingerprints</string> <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] --> <!-- Fingerprint category title - fingerprint options for unlocking the device. [CHAR LIMIT=60] -->
<string name="security_settings_category_use_fingerprint">Use fingerprint to</string> <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] --> <!-- 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" xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/security_settings_fingerprint_preference_title"> 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 <PreferenceCategory
android:key="biometric_settings_use_fingerprint_to" android:key="biometric_settings_use_fingerprint_to"
android:title="@string/security_settings_category_use_fingerprint" android:title="@string/security_settings_category_use_fingerprint"

View File

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

View File

@@ -23,6 +23,7 @@ import android.hardware.fingerprint.FingerprintManager;
import android.provider.Settings; import android.provider.Settings;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
@@ -53,6 +54,20 @@ public class FingerprintSettingsAppsPreferenceController
isChecked ? ON : OFF, getUserId()); 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 @Override
public int getAvailabilityStatus() { public int getAvailabilityStatus() {
final ActiveUnlockStatusUtils activeUnlockStatusUtils = 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 static android.provider.Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED;
import android.content.Context; import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.provider.Settings; import android.provider.Settings;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
@@ -33,9 +35,12 @@ public class FingerprintSettingsKeyguardUnlockPreferenceController
private static final int OFF = 0; private static final int OFF = 0;
private static final int DEFAULT = ON; private static final int DEFAULT = ON;
private FingerprintManager mFingerprintManager;
public FingerprintSettingsKeyguardUnlockPreferenceController( public FingerprintSettingsKeyguardUnlockPreferenceController(
@NonNull Context context, @NonNull String key) { @NonNull Context context, @NonNull String key) {
super(context, key); super(context, key);
mFingerprintManager = Utils.getFingerprintManagerOrNull(context);
} }
@Override @Override
@@ -50,6 +55,20 @@ public class FingerprintSettingsKeyguardUnlockPreferenceController
FINGERPRINT_KEYGUARD_ENABLED, isChecked ? ON : OFF, getUserId()); 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 @Override
public int getAvailabilityStatus() { public int getAvailabilityStatus() {
final ActiveUnlockStatusUtils activeUnlockStatusUtils = final ActiveUnlockStatusUtils activeUnlockStatusUtils =

View File

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