[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:
@@ -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] -->
|
||||
|
@@ -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"
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
||||
|
@@ -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 =
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -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 =
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user