[Biometric Onboarding & Edu] Update face settings page

- Added a feature provider for face settings page in FaceFeatureProvider
  for customization
- When face is deleted, disabled the settings buttons instead of hiding
  them.
- Updated new UX style for add/remove face button.

Bug: 370940762
Test: atest FaceSettingsEnrollButtonPreferenceControllerTest
            FaceSettingsFooterPreferenceControllerTest
Test: manual - 1. Enroll face
               2. Go Face Settings page and remove face
	       3. Enroll face again
Flag: com.android.settings.flags.biometrics_onboarding_education

Change-Id: I490e647523eeff2dd1a58aab07f638e3e5e0ffb8
This commit is contained in:
Shawn Lin
2025-01-27 05:16:39 +00:00
parent d6e99d3783
commit d747235a4c
14 changed files with 394 additions and 54 deletions

26
res/drawable/ic_face.xml Normal file
View File

@@ -0,0 +1,26 @@
<!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M360,570Q339,570 324.5,555.5Q310,541 310,520Q310,499 324.5,484.5Q339,470 360,470Q381,470 395.5,484.5Q410,499 410,520Q410,541 395.5,555.5Q381,570 360,570ZM600,570Q579,570 564.5,555.5Q550,541 550,520Q550,499 564.5,484.5Q579,470 600,470Q621,470 635.5,484.5Q650,499 650,520Q650,541 635.5,555.5Q621,570 600,570ZM480,800Q614,800 707,707Q800,614 800,480Q800,456 797,433.5Q794,411 786,390Q765,395 744,397.5Q723,400 700,400Q609,400 528,361Q447,322 390,252Q358,330 298.5,387.5Q239,445 160,474Q160,476 160,477Q160,478 160,480Q160,614 253,707Q346,800 480,800ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM426,165Q468,235 540,277.5Q612,320 700,320Q714,320 727,318.5Q740,317 754,315Q712,245 640,202.5Q568,160 480,160Q466,160 453,161.5Q440,163 426,165ZM177,379Q228,350 266,304Q304,258 323,201Q272,230 234,276Q196,322 177,379ZM426,165Q426,165 426,165Q426,165 426,165Q426,165 426,165Q426,165 426,165Q426,165 426,165Q426,165 426,165Q426,165 426,165Q426,165 426,165ZM323,201Q323,201 323,201Q323,201 323,201Q323,201 323,201Q323,201 323,201Z"/>
</vector>

View File

@@ -914,6 +914,14 @@
<string name="security_settings_face_enroll_improve_face_alert_body_fingerprint">Delete your current face model to set up Face Unlock again.\n\nYour face model will be permanently and securely deleted.\n\nAfter deletion, you will need your fingerprint, PIN, pattern, or password to unlock your phone or for authentication in apps.</string>
<!-- Title for a category shown for the face settings page. [CHAR LIMIT=20] -->
<string name="security_settings_face_settings_use_face_category">Use Face Unlock for</string>
<!-- Description shown on face settings page. -->
<string name="security_settings_face_description"></string>
<!-- Footer description shown on face settings page. -->
<string name="security_settings_face_footer_description_class3"></string>
<!-- Footer learn more description shown on face settings page. -->
<string name="security_settings_face_footer_learn_more_description"></string>
<!-- Footer learn more URL shown on face settings page [DO NOT TRANSLATE] -->
<string name="security_settings_face_footer_learn_more_url" translatable="false"></string>
<!-- Title for a category shown for the face settings page. [CHAR LIMIT=20] -->
<string name="security_settings_face_settings_face_category">Face</string>
<!-- Title for a category shown for the face settings page. [CHAR LIMIT=20] -->
@@ -948,6 +956,8 @@
<string name="security_settings_face_remove_dialog_details_fingerprint_conv">Your face model will be permanently and securely deleted.\n\nAfter deletion, you will need your fingerprint, PIN, pattern, or password to unlock your phone.</string>
<!-- Subtitle shown for contextual setting face enrollment [CHAR LIMIT=NONE] -->
<string name="security_settings_face_settings_context_subtitle">Use Face Unlock to unlock your phone</string>
<!-- Title shown for adding face in face settings page. -->
<string name="security_settings_face_add">Add face</string>
<!-- Fingerprint enrollment and settings --><skip />
<!-- Note: Update FingerprintEnrollParentalConsent.CONSENT_STRING_RESOURCES when any _consent_ strings are added or removed. -->

View File

@@ -19,6 +19,11 @@
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/security_settings_face_preference_title">
<com.android.settingslib.widget.TopIntroPreference
android:key="security_settings_face_description"
settings:searchable="false"
settings:isPreferenceVisible="false" />
<PreferenceCategory
android:key="biometric_settings_use_face_to"
android:title="@string/security_settings_face_settings_use_face_to_category"
@@ -37,6 +42,18 @@
settings:controller="com.android.settings.biometrics.face.FaceSettingsAppsPreferenceController"/>
</PreferenceCategory>
<PreferenceCategory
android:key="security_settings_face_enrolled_category"
android:title="@string/security_settings_face_preference_title_new"
settings:isPreferenceVisible="false">
<com.android.settings.biometrics.face.FacePreference
android:key="security_settings_face_remove"
android:title="@string/security_settings_face_preference_title_new"/>
<Preference
android:key="security_settings_face_enroll"
android:title="@string/security_settings_face_add"/>
</PreferenceCategory>
<PreferenceCategory
android:key="security_settings_face_unlock_category"
android:title="@string/security_settings_face_settings_use_face_category"
@@ -88,13 +105,15 @@
android:key="security_settings_face_delete_faces_container"
android:title="@string/security_settings_face_settings_remove_face_model"
android:selectable="false"
android:layout="@layout/face_remove_button"/>
android:layout="@layout/face_remove_button"
settings:isPreferenceVisible="false"/>
<com.android.settingslib.widget.LayoutPreference
android:key="security_settings_face_enroll_faces_container"
android:title="@string/security_settings_face_settings_enroll"
android:selectable="false"
android:layout="@layout/face_enroll_button"/>
android:layout="@layout/face_enroll_button"
settings:isPreferenceVisible="false"/>
<com.android.settingslib.widget.FooterPreference
android:key="security_face_footer"

View File

@@ -42,4 +42,13 @@ public interface FaceFeatureProvider {
default FaceEnrollActivityClassProvider getEnrollActivityClassProvider() {
return FaceEnrollActivityClassProvider.getInstance();
}
/**
* Gets the feature provider for FaceSettings page
* @return the provider
*/
@NonNull
default FaceSettingsFeatureProvider getFaceSettingsFeatureProvider() {
return FaceSettingsFeatureProvider.getInstance();
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.face;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settingslib.widget.TwoTargetPreference;
public class FacePreference extends TwoTargetPreference {
public FacePreference(@NonNull Context context) {
super(context);
}
public FacePreference(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public FacePreference(
@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public FacePreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected int getSecondTargetResId() {
return R.layout.preference_widget_delete;
}
}

View File

@@ -30,6 +30,7 @@ import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.res.ResourceId;
import android.hardware.face.FaceManager;
import android.os.Bundle;
import android.os.UserHandle;
@@ -73,12 +74,19 @@ public class FaceSettings extends DashboardFragment {
private static final String KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED =
"biometrics_successfully_authenticated";
private static final String PREF_KEY_FACE_DESCRIPTION = "security_settings_face_description";
private static final String PREF_KEY_DELETE_FACE_DATA =
"security_settings_face_delete_faces_container";
private static final String PREF_KEY_ENROLL_FACE_UNLOCK =
"security_settings_face_enroll_faces_container";
private static final String PREF_KEY_USE_FACE_TO_CATEGORY =
"biometric_settings_use_face_to";
private static final String PREF_KEY_FACE_ENROLLED_CATEGORY =
"security_settings_face_enrolled_category";
private static final String PREF_KEY_FACE_REMOVE =
"security_settings_face_remove";
private static final String PREF_KEY_FACE_ENROLL =
"security_settings_face_enroll";
public static final String SECURITY_SETTINGS_FACE_MANAGE_CATEGORY =
"security_settings_face_manage_category";
@@ -98,6 +106,9 @@ public class FaceSettings extends DashboardFragment {
private List<Preference> mTogglePreferences;
private Preference mRemoveButton;
private Preference mEnrollButton;
private PreferenceCategory mFaceEnrolledCategory;
private Preference mFaceRemoveButton;
private Preference mFaceEnrollButton;
private FaceFeatureProvider mFaceFeatureProvider;
private boolean mConfirmingPassword;
@@ -111,8 +122,7 @@ public class FaceSettings extends DashboardFragment {
}
// Hide the "remove" button and show the "set up face authentication" button.
mRemoveButton.setVisible(false);
mEnrollButton.setVisible(true);
updateFaceAddAndRemovePreference(false);
};
private final FaceSettingsEnrollButtonPreferenceController.Listener mEnrollListener = intent ->
@@ -193,6 +203,15 @@ public class FaceSettings extends DashboardFragment {
: use(FaceSettingsLockscreenBypassPreferenceController.class);
mLockscreenController.setUserId(mUserId);
final int descriptionResId = FeatureFactory.getFeatureFactory()
.getFaceFeatureProvider().getFaceSettingsFeatureProvider()
.getSettingPageDescription();
if (ResourceId.isValid(descriptionResId)) {
final Preference preference = findPreference(PREF_KEY_FACE_DESCRIPTION);
preference.setTitle(descriptionResId);
preference.setVisible(true);
}
final PreferenceCategory managePref =
findPreference(SECURITY_SETTINGS_FACE_MANAGE_CATEGORY);
Preference keyguardPref = findPreference(FaceSettingsKeyguardPreferenceController.KEY);
@@ -201,8 +220,13 @@ public class FaceSettings extends DashboardFragment {
Preference confirmPref = findPreference(FaceSettingsConfirmPreferenceController.KEY);
Preference bypassPref =
findPreference(mLockscreenController.getPreferenceKey());
Preference unlockKeyguard = findPreference(
use(FaceSettingsKeyguardUnlockPreferenceController.class).getPreferenceKey());
Preference appsPref = findPreference(
use(FaceSettingsAppsPreferenceController.class).getPreferenceKey());
mTogglePreferences = new ArrayList<>(
Arrays.asList(keyguardPref, appPref, attentionPref, confirmPref, bypassPref));
Arrays.asList(keyguardPref, appPref, attentionPref, confirmPref, bypassPref,
unlockKeyguard, appsPref));
if (RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
getContext(), DevicePolicyManager.KEYGUARD_DISABLE_FACE, mUserId) != null) {
@@ -215,9 +239,18 @@ public class FaceSettings extends DashboardFragment {
mRemoveButton = findPreference(FaceSettingsRemoveButtonPreferenceController.KEY);
mEnrollButton = findPreference(FaceSettingsEnrollButtonPreferenceController.KEY);
mFaceEnrolledCategory = findPreference(PREF_KEY_FACE_ENROLLED_CATEGORY);
mFaceRemoveButton = findPreference(PREF_KEY_FACE_REMOVE);
mFaceRemoveButton.setIcon(R.drawable.ic_face);
mFaceRemoveButton.setOnPreferenceClickListener(
use(FaceSettingsRemoveButtonPreferenceController.class));
mFaceEnrollButton = findPreference(PREF_KEY_FACE_ENROLL);
mFaceEnrollButton.setIcon(R.drawable.ic_add_24dp);
mFaceEnrollButton.setOnPreferenceClickListener(
use(FaceSettingsEnrollButtonPreferenceController.class));
final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
mEnrollButton.setVisible(!hasEnrolled);
mRemoveButton.setVisible(hasEnrolled);
updateFaceAddAndRemovePreference(hasEnrolled);
// There is no better way to do this :/
for (AbstractPreferenceController controller : mControllers) {
@@ -255,8 +288,7 @@ public class FaceSettings extends DashboardFragment {
public void onStart() {
super.onStart();
final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
mEnrollButton.setVisible(!hasEnrolled);
mRemoveButton.setVisible(hasEnrolled);
updateFaceAddAndRemovePreference(hasEnrolled);
// When the user has face id registered but failed enrolling in device lock state,
// lead users directly to the confirm deletion dialog in Face Unlock settings.
@@ -264,6 +296,11 @@ public class FaceSettings extends DashboardFragment {
final boolean isReEnrollFaceUnlock = getIntent().getBooleanExtra(
FaceSettings.KEY_RE_ENROLL_FACE, false);
if (isReEnrollFaceUnlock) {
if (Flags.biometricsOnboardingEducation()) {
if (mFaceRemoveButton.isEnabled()) {
mRemoveController.onPreferenceClick(mFaceRemoveButton);
}
} else {
final Button removeBtn = ((LayoutPreference) mRemoveButton).findViewById(
R.id.security_settings_face_settings_remove_button);
if (removeBtn != null && removeBtn.isEnabled()) {
@@ -272,6 +309,7 @@ public class FaceSettings extends DashboardFragment {
}
}
}
}
@Override
public void onResume() {
@@ -327,8 +365,7 @@ public class FaceSettings extends DashboardFragment {
});
final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
mEnrollButton.setVisible(!hasEnrolled);
mRemoveButton.setVisible(hasEnrolled);
updateFaceAddAndRemovePreference(hasEnrolled);
final Utils.BiometricStatus biometricAuthStatus =
Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
mBiometricsAuthenticationRequested,
@@ -407,6 +444,17 @@ public class FaceSettings extends DashboardFragment {
return mControllers;
}
private void updateFaceAddAndRemovePreference(boolean hasEnrolled) {
if (Flags.biometricsOnboardingEducation()) {
mFaceEnrolledCategory.setVisible(true);
mFaceRemoveButton.setVisible(hasEnrolled);
mFaceEnrollButton.setVisible(!hasEnrolled);
} else {
mEnrollButton.setVisible(!hasEnrolled);
mRemoveButton.setVisible(hasEnrolled);
}
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new FaceSettingsKeyguardPreferenceController(context));

View File

@@ -23,6 +23,7 @@ import android.hardware.face.FaceManager;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.Utils;
import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
@@ -52,6 +53,20 @@ public class FaceSettingsAppsPreferenceController extends
isChecked ? ON : OFF, getUserId());
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if (!FaceSettings.isFaceHardwareDetected(mContext)) {
preference.setEnabled(false);
} else if (!mFaceManager.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

@@ -24,10 +24,12 @@ import android.content.Intent;
import android.view.View;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.flags.Flags;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.widget.LayoutPreference;
@@ -39,10 +41,11 @@ import com.google.android.setupdesign.util.PartnerStyleHelper;
* Preference controller that allows a user to enroll their face.
*/
public class FaceSettingsEnrollButtonPreferenceController extends BasePreferenceController
implements View.OnClickListener {
implements View.OnClickListener, Preference.OnPreferenceClickListener {
private static final String TAG = "FaceSettings/Remove";
static final String KEY = "security_settings_face_enroll_faces_container";
static final String KEY_1 = "security_settings_face_enroll";
private final Context mContext;
@@ -53,7 +56,7 @@ public class FaceSettingsEnrollButtonPreferenceController extends BasePreference
private Listener mListener;
public FaceSettingsEnrollButtonPreferenceController(Context context) {
this(context, KEY);
this(context, Flags.biometricsOnboardingEducation() ? KEY_1 : KEY);
}
public FaceSettingsEnrollButtonPreferenceController(Context context, String preferenceKey) {
@@ -62,9 +65,15 @@ public class FaceSettingsEnrollButtonPreferenceController extends BasePreference
}
@Override
public void updateState(Preference preference) {
public void updateState(@NonNull Preference preference) {
super.updateState(preference);
final boolean isDeviceOwnerBlockingAuth =
RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
mContext, DevicePolicyManager.KEYGUARD_DISABLE_FACE, mUserId) != null;
if (Flags.biometricsOnboardingEducation()) {
preference.setEnabled(!isDeviceOwnerBlockingAuth);
} else {
mButton = ((LayoutPreference) preference).findViewById(
R.id.security_settings_face_settings_enroll_button);
@@ -73,14 +82,22 @@ public class FaceSettingsEnrollButtonPreferenceController extends BasePreference
}
mButton.setOnClickListener(this);
final boolean isDeviceOwnerBlockingAuth =
RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
mContext, DevicePolicyManager.KEYGUARD_DISABLE_FACE, mUserId) != null;
mButton.setEnabled(!isDeviceOwnerBlockingAuth);
}
}
@Override
public void onClick(View v) {
startEnrolling();
}
@Override
public boolean onPreferenceClick(@NonNull Preference preference) {
startEnrolling();
return true;
}
private void startEnrolling() {
mIsClicked = true;
final Intent intent = new Intent();
intent.setClassName(SETTINGS_PACKAGE_NAME, FaceEnroll.class.getName());

View File

@@ -0,0 +1,55 @@
/*
* 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.face
/**
* Provide features for FaceSettings page.
*/
open class FaceSettingsFeatureProvider {
/**
* Get the description shown in the Face settings page.
*/
open fun getSettingPageDescription(): Int {
return 0
}
/**
* Get the footer description for face class3 shown in the Face settings page.
*/
open fun getSettingPageFooterDescriptionClass3(): Int {
return com.android.settings.R.string.security_settings_face_settings_footer_class3
}
/**
* Get the footer learn more description shown in the Face settings page.
*/
open fun getSettingPageFooterLearnMoreDescription(): Int {
return 0
}
/**
* Get the footer learn more URL.
*/
open fun getSettingPageFooterLearnMoreUrl(): Int {
return 0
}
companion object {
@JvmStatic
val instance = FaceSettingsFeatureProvider()
}
}

View File

@@ -19,11 +19,13 @@ package com.android.settings.biometrics.face;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ResourceId;
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
@@ -35,6 +37,7 @@ import com.android.settings.core.BasePreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.utils.AnnotationSpan;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.widget.FooterPreference;
import java.util.List;
@@ -82,14 +85,15 @@ public class FaceSettingsFooterPreferenceController extends BasePreferenceContro
mContext, mContext.getString(R.string.help_url_face), getClass().getName());
final AnnotationSpan.LinkInfo linkInfo =
new AnnotationSpan.LinkInfo(mContext, ANNOTATION_URL, helpIntent);
int footerRes;
final FaceSettingsFeatureProvider featureProvider = FeatureFactory.getFeatureFactory()
.getFaceFeatureProvider().getFaceSettingsFeatureProvider();
final int footerRes;
boolean isAttentionSupported = mProvider.isAttentionSupported(mContext);
if (Utils.isPrivateProfile(mUserId, mContext)) {
footerRes = R.string.private_space_face_settings_footer;
} else if (mIsFaceStrong) {
footerRes = isAttentionSupported
? R.string.security_settings_face_settings_footer_class3
? featureProvider.getSettingPageFooterDescriptionClass3()
: R.string.security_settings_face_settings_footer_attention_not_supported;
} else {
footerRes = isAttentionSupported
@@ -98,6 +102,20 @@ public class FaceSettingsFooterPreferenceController extends BasePreferenceContro
}
preference.setTitle(AnnotationSpan.linkify(
mContext.getText(footerRes), linkInfo));
final int learnMoreRes = featureProvider.getSettingPageFooterLearnMoreDescription();
final int learnMoreUrlRes = featureProvider.getSettingPageFooterLearnMoreUrl();
if (ResourceId.isValid(learnMoreRes)
&& ResourceId.isValid(learnMoreUrlRes)
&& preference instanceof FooterPreference) {
final Intent learnMoreIntent = HelpUtils.getHelpIntent(
mContext, mContext.getString(learnMoreUrlRes), getClass().getName());
final View.OnClickListener learnMoreClickListener = (v) -> {
mContext.startActivityForResult(KEY, learnMoreIntent, 0, null);
};
((FooterPreference) preference).setLearnMoreAction(learnMoreClickListener);
((FooterPreference) preference).setLearnMoreText(mContext.getString(learnMoreRes));
}
}
public void setUserId(int userId) {

View File

@@ -19,9 +19,11 @@ package com.android.settings.biometrics.face;
import static android.provider.Settings.Secure.FACE_KEYGUARD_ENABLED;
import android.content.Context;
import android.hardware.face.FaceManager;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.Utils;
import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
@@ -32,9 +34,12 @@ public class FaceSettingsKeyguardUnlockPreferenceController extends
private static final int OFF = 0;
private static final int DEFAULT = ON;
private FaceManager mFaceManager;
public FaceSettingsKeyguardUnlockPreferenceController(
@NonNull Context context, @NonNull String key) {
super(context, key);
mFaceManager = Utils.getFaceManagerOrNull(context);
}
@Override
@@ -49,6 +54,20 @@ public class FaceSettingsKeyguardUnlockPreferenceController extends
FACE_KEYGUARD_ENABLED, isChecked ? ON : OFF, getUserId());
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if (!FaceSettings.isFaceHardwareDetected(mContext)) {
preference.setEnabled(false);
} else if (!mFaceManager.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

@@ -31,6 +31,7 @@ import android.widget.Button;
import android.widget.Toast;
import android.window.OnBackInvokedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -40,6 +41,7 @@ import com.android.settings.SettingsActivity;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.flags.Flags;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.widget.LayoutPreference;
@@ -54,10 +56,11 @@ import java.util.List;
* will likely change if multiple enrollments are allowed/supported.
*/
public class FaceSettingsRemoveButtonPreferenceController extends BasePreferenceController
implements View.OnClickListener {
implements View.OnClickListener, Preference.OnPreferenceClickListener {
private static final String TAG = "FaceSettings/Remove";
static final String KEY = "security_settings_face_delete_faces_container";
static final String KEY_1 = "security_settings_face_remove";
public static class ConfirmRemoveDialog extends InstrumentedDialogFragment
implements OnBackInvokedCallback {
@@ -173,7 +176,11 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
if (remaining == 0) {
final List<Face> faces = mFaceManager.getEnrolledFaces(mUserId);
if (!faces.isEmpty()) {
if (Flags.biometricsOnboardingEducation()) {
mPreference.setEnabled(true);
} else {
mButton.setEnabled(true);
}
} else {
mRemoving = false;
mListener.onRemoved();
@@ -189,7 +196,11 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
if (Flags.biometricsOnboardingEducation()) {
mPreference.setEnabled(false);
} else {
mButton.setEnabled(false);
}
final List<Face> faces = mFaceManager.getEnrolledFaces(mUserId);
if (faces.isEmpty()) {
Log.e(TAG, "No faces");
@@ -201,8 +212,12 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
// Remove the first/only face
mFaceUpdater.remove(faces.get(0), mUserId, mRemovalCallback);
} else {
if (Flags.biometricsOnboardingEducation()) {
mPreference.setEnabled(true);
} else {
mButton.setEnabled(true);
}
mRemoving = false;
}
@@ -224,7 +239,7 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
}
public FaceSettingsRemoveButtonPreferenceController(Context context) {
this(context, KEY);
this(context, Flags.biometricsOnboardingEducation() ? KEY_1 : KEY);
}
public void setUserId(int userId) {
@@ -232,10 +247,11 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
}
@Override
public void updateState(Preference preference) {
public void updateState(@NonNull Preference preference) {
super.updateState(preference);
mPreference = preference;
if (!Flags.biometricsOnboardingEducation()) {
mButton = ((LayoutPreference) preference)
.findViewById(R.id.security_settings_face_settings_remove_button);
@@ -244,6 +260,7 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
}
mButton.setOnClickListener(this);
}
// If there is already a ConfirmRemoveDialog showing, reset the listener since the
// controller has been recreated.
@@ -256,12 +273,21 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
removeDialog.setOnClickListener(mOnConfirmDialogClickListener);
}
if (!FaceSettings.isFaceHardwareDetected(mContext)) {
final boolean isFaceHardwareDetected = FaceSettings.isFaceHardwareDetected(mContext);
if (Flags.biometricsOnboardingEducation()) {
if (!isFaceHardwareDetected) {
mPreference.setEnabled(false);
} else {
mPreference.setEnabled(!mRemoving);
}
} else {
if (!isFaceHardwareDetected) {
mButton.setEnabled(false);
} else {
mButton.setEnabled(!mRemoving);
}
}
}
@Override
public int getAvailabilityStatus() {
@@ -270,12 +296,23 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
@Override
public String getPreferenceKey() {
return KEY;
return Flags.biometricsOnboardingEducation() ? KEY_1 : KEY;
}
@Override
public void onClick(View v) {
if (v == mButton) {
showRemoveDialog();
}
}
@Override
public boolean onPreferenceClick(@NonNull Preference preference) {
showRemoveDialog();
return true;
}
private void showRemoveDialog() {
mMetricsFeatureProvider.logClickedPreference(mPreference, getMetricsCategory());
mRemoving = true;
ConfirmRemoveDialog confirmRemoveDialog =
@@ -284,7 +321,6 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
confirmRemoveDialog.show(mActivity.getSupportFragmentManager(),
ConfirmRemoveDialog.class.getName());
}
}
public void setListener(Listener listener) {
mListener = listener;

View File

@@ -32,6 +32,8 @@ import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.os.Looper;
import android.os.RemoteException;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
@@ -60,6 +62,8 @@ import java.util.List;
public class FaceSettingsFooterPreferenceControllerTest {
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String PREF_KEY = "security_face_footer";
@Mock
private FaceManager mFaceManager;
@@ -140,6 +144,7 @@ public class FaceSettingsFooterPreferenceControllerTest {
}
@Test
@DisableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
public void testString_faceClass3() throws RemoteException {
setupHasFaceFeature();
displayFaceSettingsFooterPreferenceController();

View File

@@ -24,6 +24,7 @@ import static org.mockito.Mockito.verify;
import android.content.Context;
import android.widget.Button;
import androidx.preference.Preference;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
@@ -43,6 +44,8 @@ public class FaceSettingsEnrollButtonPreferenceControllerTest {
@Mock
private Button mButton;
@Mock
private Preference mPreference;
@Mock
private FaceSettingsEnrollButtonPreferenceController.Listener mListener;
private FaceSettingsEnrollButtonPreferenceController mController;
@@ -65,4 +68,12 @@ public class FaceSettingsEnrollButtonPreferenceControllerTest {
assertThat(mController.isClicked()).isTrue();
verify(mListener).onStartEnrolling(any());
}
@Test
public void testOnPreferenceClick() {
mController.onPreferenceClick(mPreference);
assertThat(mController.isClicked()).isTrue();
verify(mListener).onStartEnrolling(any());
}
}