Loading res/values/config.xml +3 −0 Original line number Diff line number Diff line Loading @@ -848,4 +848,7 @@ <!-- Disable the Testing Setting Menu for user builds, i.e only display the menu on userdebug/eng builds --> <bool name="config_hide_testing_settings_menu_for_user_builds">false</bool> <!-- Whether the Gaze is enabled --> <bool name="config_gazeEnabled">false</bool> </resources> res/values/strings.xml +4 −0 Original line number Diff line number Diff line Loading @@ -924,6 +924,10 @@ <string name="security_settings_face_settings_require_attention">Require eyes to be open</string> <!-- Text shown on the details of a toggle which disables/enables face unlock, depending if the user's eyes are open. [CHAR LIMIT=70] --> <string name="security_settings_face_settings_require_attention_details">To unlock the phone, your eyes must be open</string> <!-- Text shown on a toggle which disables/enables face unlock, depending if the gaze supported. [CHAR LIMIT=30] --> <string name="security_settings_face_settings_gaze"></string> <!-- Text shown on the details of a toggle which disables/enables face unlock, depending if the gaze supported. [CHAR LIMIT=70] --> <string name="security_settings_face_settings_gaze_details"></string> <!-- When authenticating in apps, always require confirmation (e.g. confirm button) after a face is authenticated. [CHAR LIMIT=50] --> <string name="security_settings_face_settings_require_confirmation">Always require confirmation</string> <!-- When authenticating in apps, always require confirmation (e.g. confirm button) after a face is authenticated. [CHAR LIMIT=70] --> src/com/android/settings/biometrics/face/FaceAttentionController.java 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * 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 static android.hardware.biometrics.BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION; import android.content.Context; import android.hardware.face.FaceManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settings.Utils; public class FaceAttentionController { @Nullable private byte[] mToken; private FaceManager mFaceManager; public interface OnSetAttentionListener { /** * Calls when setting attention is completed from FaceManager. */ void onSetAttentionCompleted(boolean success); } public interface OnGetAttentionListener { /** * Calls when getting attention is completed from FaceManager. */ void onGetAttentionCompleted(boolean success, boolean enabled); } @Nullable private OnSetAttentionListener mSetListener; @Nullable private OnGetAttentionListener mGetListener; private final FaceManager.SetFeatureCallback mSetFeatureCallback = new FaceManager.SetFeatureCallback() { @Override public void onCompleted(boolean success, int feature) { if (feature == FEATURE_REQUIRE_ATTENTION) { if (mSetListener != null) { mSetListener.onSetAttentionCompleted(success); } } } }; private final FaceManager.GetFeatureCallback mGetFeatureCallback = new FaceManager.GetFeatureCallback() { @Override public void onCompleted( boolean success, @NonNull int[] features, @NonNull boolean[] featureState) { boolean requireAttentionEnabled = false; for (int i = 0; i < features.length; i++) { if (features[i] == FEATURE_REQUIRE_ATTENTION) { requireAttentionEnabled = featureState[i]; } } if (mGetListener != null) { mGetListener.onGetAttentionCompleted(success, requireAttentionEnabled); } } }; public FaceAttentionController(@NonNull Context context) { mFaceManager = Utils.getFaceManagerOrNull(context); } /** * Set the challenge token */ public void setToken(@Nullable byte[] token) { mToken = token; } /** * Get the gaze status */ public void getAttentionStatus(int userId, @Nullable OnGetAttentionListener listener) { mGetListener = listener; mFaceManager.getFeature(userId, FEATURE_REQUIRE_ATTENTION, mGetFeatureCallback); } /** * Set the gaze status */ public void setAttentionStatus( int userId, boolean enabled, @Nullable OnSetAttentionListener listener) { mSetListener = listener; mFaceManager.setFeature(userId, FEATURE_REQUIRE_ATTENTION, enabled, mToken, mSetFeatureCallback); } } src/com/android/settings/biometrics/face/FaceEnrollEducation.java +12 −6 Original line number Diff line number Diff line Loading @@ -69,6 +69,7 @@ public class FaceEnrollEducation extends BiometricEnrollBase { private View mIllustrationAccessibility; private Intent mResultIntent; private boolean mAccessibilityEnabled; protected Intent mExtraInfoIntent; private final CompoundButton.OnCheckedChangeListener mSwitchDiversityListener = new CompoundButton.OnCheckedChangeListener() { Loading Loading @@ -171,12 +172,7 @@ public class FaceEnrollEducation extends BiometricEnrollBase { mFooterBarMixin.setPrimaryButton(footerButton); final Button accessibilityButton = findViewById(R.id.accessibility_button); accessibilityButton.setOnClickListener(view -> { mSwitchDiversity.setChecked(true); accessibilityButton.setVisibility(View.GONE); mSwitchDiversity.setVisibility(View.VISIBLE); mSwitchDiversity.addOnLayoutChangeListener(mSwitchDiversityOnLayoutChangeListener); }); accessibilityButton.setOnClickListener(this::onAccessibilityButtonClicked); mSwitchDiversity = findViewById(R.id.toggle_diversity); mSwitchDiversity.setListener(mSwitchDiversityListener); Loading Loading @@ -263,6 +259,9 @@ public class FaceEnrollEducation extends BiometricEnrollBase { if (mResultIntent != null) { intent.putExtras(mResultIntent); } if (mExtraInfoIntent != null) { intent.putExtras(mExtraInfoIntent); } intent.putExtra(EXTRA_KEY_REQUIRE_DIVERSITY, !mSwitchDiversity.isChecked()); intent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON, Loading @@ -282,6 +281,13 @@ public class FaceEnrollEducation extends BiometricEnrollBase { } protected void onAccessibilityButtonClicked(View view) { mSwitchDiversity.setChecked(true); view.setVisibility(View.GONE); mSwitchDiversity.setVisibility(View.VISIBLE); mSwitchDiversity.addOnLayoutChangeListener(mSwitchDiversityOnLayoutChangeListener); } protected void onSkipButtonClick(View view) { if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, "edu_skip")) { Loading src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java +29 −37 Original line number Diff line number Diff line Loading @@ -16,12 +16,8 @@ package com.android.settings.biometrics.face; import static android.hardware.biometrics.BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION; import android.content.Context; import android.hardware.face.FaceManager; import android.hardware.face.FaceManager.GetFeatureCallback; import android.hardware.face.FaceManager.SetFeatureCallback; import android.provider.Settings; import androidx.annotation.Nullable; Loading @@ -31,6 +27,7 @@ import androidx.preference.TwoStatePreference; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.flags.Flags; /** * Preference controller that manages the ability to use face authentication with/without Loading @@ -40,14 +37,13 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe public static final String KEY = "security_settings_face_require_attention"; private byte[] mToken; private FaceManager mFaceManager; private TwoStatePreference mPreference; private boolean mGazeEnabled; private final SetFeatureCallback mSetFeatureCallback = new SetFeatureCallback() { @Override public void onCompleted(boolean success, int feature) { if (feature == FEATURE_REQUIRE_ATTENTION) { private FaceAttentionController mFaceAttentionController; private final FaceAttentionController.OnSetAttentionListener mSetAttentionListener = (success) -> { mPreference.setEnabled(true); if (!success) { mPreference.setChecked(!mPreference.isChecked()); Loading @@ -56,31 +52,23 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, mPreference.isChecked() ? 1 : 0, getUserId()); } } } }; private final GetFeatureCallback mGetFeatureCallback = new GetFeatureCallback() { @Override public void onCompleted(boolean success, int[] features, boolean[] featureState) { boolean requireAttentionEnabled = false; for (int i = 0; i < features.length; i++) { if (features[i] == FEATURE_REQUIRE_ATTENTION) { requireAttentionEnabled = featureState[i]; } } private final FaceAttentionController.OnGetAttentionListener mOnGetAttentionListener = (success, requireAttentionEnabled) -> { mPreference.setChecked(requireAttentionEnabled); if (getRestrictingAdmin() != null) { mPreference.setEnabled(false); } else { mPreference.setEnabled(success); } } }; public FaceSettingsAttentionPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mFaceManager = Utils.getFaceManagerOrNull(context); mFaceAttentionController = new FaceAttentionController(context); mGazeEnabled = context.getResources().getBoolean(R.bool.config_gazeEnabled) && Flags.biometricsOnboardingEducation(); } public FaceSettingsAttentionPreferenceController(Context context) { Loading @@ -88,7 +76,9 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe } public void setToken(byte[] token) { mToken = token; if (mFaceAttentionController != null) { mFaceAttentionController.setToken(token); } } /** Loading @@ -109,6 +99,11 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe if (Utils.isPrivateProfile(getUserId(), mContext)) { preference.setSummary(mContext.getString( R.string.private_space_face_settings_require_attention_details)); } else if (mGazeEnabled) { preference.setTitle(mContext.getString( R.string.security_settings_face_settings_gaze)); preference.setSummary(mContext.getString( R.string.security_settings_face_settings_gaze_details)); } } Loading @@ -119,8 +114,7 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe } // Set to disabled until we know the true value. mPreference.setEnabled(false); mFaceManager.getFeature(getUserId(), FEATURE_REQUIRE_ATTENTION, mGetFeatureCallback); mFaceAttentionController.getAttentionStatus(getUserId(), mOnGetAttentionListener); // Ideally returns a cached value. return true; Loading @@ -131,9 +125,7 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe // Optimistically update state and set to disabled until we know it succeeded. mPreference.setEnabled(false); mPreference.setChecked(isChecked); mFaceManager.setFeature(getUserId(), FEATURE_REQUIRE_ATTENTION, isChecked, mToken, mSetFeatureCallback); mFaceAttentionController.setAttentionStatus(getUserId(), isChecked, mSetAttentionListener); return true; } Loading Loading
res/values/config.xml +3 −0 Original line number Diff line number Diff line Loading @@ -848,4 +848,7 @@ <!-- Disable the Testing Setting Menu for user builds, i.e only display the menu on userdebug/eng builds --> <bool name="config_hide_testing_settings_menu_for_user_builds">false</bool> <!-- Whether the Gaze is enabled --> <bool name="config_gazeEnabled">false</bool> </resources>
res/values/strings.xml +4 −0 Original line number Diff line number Diff line Loading @@ -924,6 +924,10 @@ <string name="security_settings_face_settings_require_attention">Require eyes to be open</string> <!-- Text shown on the details of a toggle which disables/enables face unlock, depending if the user's eyes are open. [CHAR LIMIT=70] --> <string name="security_settings_face_settings_require_attention_details">To unlock the phone, your eyes must be open</string> <!-- Text shown on a toggle which disables/enables face unlock, depending if the gaze supported. [CHAR LIMIT=30] --> <string name="security_settings_face_settings_gaze"></string> <!-- Text shown on the details of a toggle which disables/enables face unlock, depending if the gaze supported. [CHAR LIMIT=70] --> <string name="security_settings_face_settings_gaze_details"></string> <!-- When authenticating in apps, always require confirmation (e.g. confirm button) after a face is authenticated. [CHAR LIMIT=50] --> <string name="security_settings_face_settings_require_confirmation">Always require confirmation</string> <!-- When authenticating in apps, always require confirmation (e.g. confirm button) after a face is authenticated. [CHAR LIMIT=70] -->
src/com/android/settings/biometrics/face/FaceAttentionController.java 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * 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 static android.hardware.biometrics.BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION; import android.content.Context; import android.hardware.face.FaceManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settings.Utils; public class FaceAttentionController { @Nullable private byte[] mToken; private FaceManager mFaceManager; public interface OnSetAttentionListener { /** * Calls when setting attention is completed from FaceManager. */ void onSetAttentionCompleted(boolean success); } public interface OnGetAttentionListener { /** * Calls when getting attention is completed from FaceManager. */ void onGetAttentionCompleted(boolean success, boolean enabled); } @Nullable private OnSetAttentionListener mSetListener; @Nullable private OnGetAttentionListener mGetListener; private final FaceManager.SetFeatureCallback mSetFeatureCallback = new FaceManager.SetFeatureCallback() { @Override public void onCompleted(boolean success, int feature) { if (feature == FEATURE_REQUIRE_ATTENTION) { if (mSetListener != null) { mSetListener.onSetAttentionCompleted(success); } } } }; private final FaceManager.GetFeatureCallback mGetFeatureCallback = new FaceManager.GetFeatureCallback() { @Override public void onCompleted( boolean success, @NonNull int[] features, @NonNull boolean[] featureState) { boolean requireAttentionEnabled = false; for (int i = 0; i < features.length; i++) { if (features[i] == FEATURE_REQUIRE_ATTENTION) { requireAttentionEnabled = featureState[i]; } } if (mGetListener != null) { mGetListener.onGetAttentionCompleted(success, requireAttentionEnabled); } } }; public FaceAttentionController(@NonNull Context context) { mFaceManager = Utils.getFaceManagerOrNull(context); } /** * Set the challenge token */ public void setToken(@Nullable byte[] token) { mToken = token; } /** * Get the gaze status */ public void getAttentionStatus(int userId, @Nullable OnGetAttentionListener listener) { mGetListener = listener; mFaceManager.getFeature(userId, FEATURE_REQUIRE_ATTENTION, mGetFeatureCallback); } /** * Set the gaze status */ public void setAttentionStatus( int userId, boolean enabled, @Nullable OnSetAttentionListener listener) { mSetListener = listener; mFaceManager.setFeature(userId, FEATURE_REQUIRE_ATTENTION, enabled, mToken, mSetFeatureCallback); } }
src/com/android/settings/biometrics/face/FaceEnrollEducation.java +12 −6 Original line number Diff line number Diff line Loading @@ -69,6 +69,7 @@ public class FaceEnrollEducation extends BiometricEnrollBase { private View mIllustrationAccessibility; private Intent mResultIntent; private boolean mAccessibilityEnabled; protected Intent mExtraInfoIntent; private final CompoundButton.OnCheckedChangeListener mSwitchDiversityListener = new CompoundButton.OnCheckedChangeListener() { Loading Loading @@ -171,12 +172,7 @@ public class FaceEnrollEducation extends BiometricEnrollBase { mFooterBarMixin.setPrimaryButton(footerButton); final Button accessibilityButton = findViewById(R.id.accessibility_button); accessibilityButton.setOnClickListener(view -> { mSwitchDiversity.setChecked(true); accessibilityButton.setVisibility(View.GONE); mSwitchDiversity.setVisibility(View.VISIBLE); mSwitchDiversity.addOnLayoutChangeListener(mSwitchDiversityOnLayoutChangeListener); }); accessibilityButton.setOnClickListener(this::onAccessibilityButtonClicked); mSwitchDiversity = findViewById(R.id.toggle_diversity); mSwitchDiversity.setListener(mSwitchDiversityListener); Loading Loading @@ -263,6 +259,9 @@ public class FaceEnrollEducation extends BiometricEnrollBase { if (mResultIntent != null) { intent.putExtras(mResultIntent); } if (mExtraInfoIntent != null) { intent.putExtras(mExtraInfoIntent); } intent.putExtra(EXTRA_KEY_REQUIRE_DIVERSITY, !mSwitchDiversity.isChecked()); intent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON, Loading @@ -282,6 +281,13 @@ public class FaceEnrollEducation extends BiometricEnrollBase { } protected void onAccessibilityButtonClicked(View view) { mSwitchDiversity.setChecked(true); view.setVisibility(View.GONE); mSwitchDiversity.setVisibility(View.VISIBLE); mSwitchDiversity.addOnLayoutChangeListener(mSwitchDiversityOnLayoutChangeListener); } protected void onSkipButtonClick(View view) { if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, "edu_skip")) { Loading
src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java +29 −37 Original line number Diff line number Diff line Loading @@ -16,12 +16,8 @@ package com.android.settings.biometrics.face; import static android.hardware.biometrics.BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION; import android.content.Context; import android.hardware.face.FaceManager; import android.hardware.face.FaceManager.GetFeatureCallback; import android.hardware.face.FaceManager.SetFeatureCallback; import android.provider.Settings; import androidx.annotation.Nullable; Loading @@ -31,6 +27,7 @@ import androidx.preference.TwoStatePreference; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.flags.Flags; /** * Preference controller that manages the ability to use face authentication with/without Loading @@ -40,14 +37,13 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe public static final String KEY = "security_settings_face_require_attention"; private byte[] mToken; private FaceManager mFaceManager; private TwoStatePreference mPreference; private boolean mGazeEnabled; private final SetFeatureCallback mSetFeatureCallback = new SetFeatureCallback() { @Override public void onCompleted(boolean success, int feature) { if (feature == FEATURE_REQUIRE_ATTENTION) { private FaceAttentionController mFaceAttentionController; private final FaceAttentionController.OnSetAttentionListener mSetAttentionListener = (success) -> { mPreference.setEnabled(true); if (!success) { mPreference.setChecked(!mPreference.isChecked()); Loading @@ -56,31 +52,23 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, mPreference.isChecked() ? 1 : 0, getUserId()); } } } }; private final GetFeatureCallback mGetFeatureCallback = new GetFeatureCallback() { @Override public void onCompleted(boolean success, int[] features, boolean[] featureState) { boolean requireAttentionEnabled = false; for (int i = 0; i < features.length; i++) { if (features[i] == FEATURE_REQUIRE_ATTENTION) { requireAttentionEnabled = featureState[i]; } } private final FaceAttentionController.OnGetAttentionListener mOnGetAttentionListener = (success, requireAttentionEnabled) -> { mPreference.setChecked(requireAttentionEnabled); if (getRestrictingAdmin() != null) { mPreference.setEnabled(false); } else { mPreference.setEnabled(success); } } }; public FaceSettingsAttentionPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mFaceManager = Utils.getFaceManagerOrNull(context); mFaceAttentionController = new FaceAttentionController(context); mGazeEnabled = context.getResources().getBoolean(R.bool.config_gazeEnabled) && Flags.biometricsOnboardingEducation(); } public FaceSettingsAttentionPreferenceController(Context context) { Loading @@ -88,7 +76,9 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe } public void setToken(byte[] token) { mToken = token; if (mFaceAttentionController != null) { mFaceAttentionController.setToken(token); } } /** Loading @@ -109,6 +99,11 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe if (Utils.isPrivateProfile(getUserId(), mContext)) { preference.setSummary(mContext.getString( R.string.private_space_face_settings_require_attention_details)); } else if (mGazeEnabled) { preference.setTitle(mContext.getString( R.string.security_settings_face_settings_gaze)); preference.setSummary(mContext.getString( R.string.security_settings_face_settings_gaze_details)); } } Loading @@ -119,8 +114,7 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe } // Set to disabled until we know the true value. mPreference.setEnabled(false); mFaceManager.getFeature(getUserId(), FEATURE_REQUIRE_ATTENTION, mGetFeatureCallback); mFaceAttentionController.getAttentionStatus(getUserId(), mOnGetAttentionListener); // Ideally returns a cached value. return true; Loading @@ -131,9 +125,7 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe // Optimistically update state and set to disabled until we know it succeeded. mPreference.setEnabled(false); mPreference.setChecked(isChecked); mFaceManager.setFeature(getUserId(), FEATURE_REQUIRE_ATTENTION, isChecked, mToken, mSetFeatureCallback); mFaceAttentionController.setAttentionStatus(getUserId(), isChecked, mSetAttentionListener); return true; } Loading