From 33863f8eda3314b370409c03596d2d2306c1c370 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Sat, 4 Jan 2025 08:05:22 +0000 Subject: [PATCH] [Gaze] Support Gaze - Add gaze configs and resources - Extract the logic for setting/getting gaze out from FaceSettingsAttentionPreferenceController to FaceAttentionController Bug: 388686801 Test: make Flag: com.android.settings.flags.biometrics_onboarding_education Change-Id: I2f45c7b01674a28f7f02f614292331ced355cc6f --- res/values/config.xml | 3 + res/values/strings.xml | 4 + .../face/FaceAttentionController.java | 109 ++++++++++++++++++ .../biometrics/face/FaceEnrollEducation.java | 18 ++- ...SettingsAttentionPreferenceController.java | 64 +++++----- 5 files changed, 156 insertions(+), 42 deletions(-) create mode 100644 src/com/android/settings/biometrics/face/FaceAttentionController.java 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; }