diff --git a/res/values/config.xml b/res/values/config.xml
index 95f8eba8f84..9ad203505ed 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -845,4 +845,7 @@
false
+
+
+ false
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ce2e3985b4e..b3699271e51 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -924,6 +924,10 @@
Require eyes to be open
To unlock the phone, your eyes must be open
+
+
+
+
Always require confirmation
diff --git a/src/com/android/settings/biometrics/face/FaceAttentionController.java b/src/com/android/settings/biometrics/face/FaceAttentionController.java
new file mode 100644
index 00000000000..f035dfd0719
--- /dev/null
+++ b/src/com/android/settings/biometrics/face/FaceAttentionController.java
@@ -0,0 +1,109 @@
+/*
+ * 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);
+ }
+}
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollEducation.java b/src/com/android/settings/biometrics/face/FaceEnrollEducation.java
index 6862bc921d7..7b0b8ef0f56 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollEducation.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollEducation.java
@@ -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() {
@@ -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);
@@ -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,
@@ -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")) {
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java
index 71c46787261..c7e2141c53a 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java
@@ -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;
@@ -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
@@ -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());
@@ -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);
}
- }
- 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) {
@@ -88,7 +76,9 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe
}
public void setToken(byte[] token) {
- mToken = token;
+ if (mFaceAttentionController != null) {
+ mFaceAttentionController.setToken(token);
+ }
}
/**
@@ -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));
}
}
@@ -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;
@@ -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;
}