From 0b7d46746cae142f11f15e7402ee1f7478219641 Mon Sep 17 00:00:00 2001 From: josephpv Date: Fri, 8 Dec 2023 16:11:25 +0000 Subject: [PATCH] Add Biometrics settings inside private space One Lock settings This includes below changes: - Add Biometrics preferernce in Private Space One Lock settings page - Face and Fingerprint enrolment for Private profile - Feature is behind flag android.multiuser.enable_biometrics_to_unlock_private_space Screenshots: go/ss/9cWZRAuvGGW7gMm.png go/ss/B3NoFbL3KbpSzeN.png go/ss/7xH3DLi9d6Lu2mR.png go/ss/8WBEgKychWpduXg.png go/ss/8pmPGshd9aiPvVC.png go/ss/AFPBPbiaBBvTj3p.png go/ss/3fE4XKLLUdP8LmF.png Bug: 308862923 Test: atest CombinedBiometricStatusUtilsTest, atest FaceFingerprintUnlockControllerTest Change-Id: I1853107a4df4fc97db53c97524c6d44a4f554e37 --- AndroidManifest.xml | 7 + res/values/strings.xml | 6 + res/xml/private_space_biometric_settings.xml | 47 +++++++ res/xml/privatespace_one_lock.xml | 6 +- src/com/android/settings/Settings.java | 4 +- .../CombinedBiometricStatusUtils.java | 8 ++ .../core/gateway/SettingsGateway.java | 2 + .../FaceFingerprintUnlockController.java | 49 +++++-- .../PrivateSpaceBiometricSettings.java | 84 ++++++++++++ .../PrivateSpaceFacePreferenceController.java | 65 +++++++++ ...eSpaceFingerprintPreferenceController.java | 67 +++++++++ .../onelock/UseOneLockControllerSwitch.java | 7 +- .../onelock/UseOneLockSettingsFragment.java | 4 +- .../CombinedBiometricStatusUtilsTest.java | 15 ++ .../FaceFingerprintUnlockControllerTest.java | 128 ++++++++++++++++++ 15 files changed, 477 insertions(+), 22 deletions(-) create mode 100644 res/xml/private_space_biometric_settings.xml create mode 100644 src/com/android/settings/privatespace/onelock/PrivateSpaceBiometricSettings.java create mode 100644 src/com/android/settings/privatespace/onelock/PrivateSpaceFacePreferenceController.java create mode 100644 src/com/android/settings/privatespace/onelock/PrivateSpaceFingerprintPreferenceController.java create mode 100644 tests/unit/src/com/android/settings/privatespace/FaceFingerprintUnlockControllerTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a3acd49d958..321c4375ad4 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -719,6 +719,13 @@ android:value="@string/menu_key_security"/> + + + + Face & Fingerprint Unlock Tap to set up + + Fingerprint Unlock for Private Space + + Face Unlock for Private Space + + Ways to unlock Same as device screen lock diff --git a/res/xml/private_space_biometric_settings.xml b/res/xml/private_space_biometric_settings.xml new file mode 100644 index 00000000000..6135b5ff7dd --- /dev/null +++ b/res/xml/private_space_biometric_settings.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/privatespace_one_lock.xml b/res/xml/privatespace_one_lock.xml index e078c17b002..c9e0e6207ee 100644 --- a/res/xml/privatespace_one_lock.xml +++ b/res/xml/privatespace_one_lock.xml @@ -34,11 +34,11 @@ android:summary="@string/unlock_set_unlock_mode_pattern" settings:searchable="false" /> - diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 86baba4cd90..01ccbb292ad 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -57,7 +57,9 @@ public class Settings extends SettingsActivity { public static class CombinedBiometricProfileSettingsActivity extends SettingsActivity { /* empty */ } public static class TetherSettingsActivity extends SettingsActivity { /* empty */ } public static class WifiTetherSettingsActivity extends SettingsActivity { /* empty */ } - + public static class PrivateSpaceBiometricSettingsActivity extends SettingsActivity { + /* empty */ + } public static class VpnSettingsActivity extends SettingsActivity { /* empty */ } /** Activity for Data saver settings. */ public static class DataSaverSummaryActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java index 8cc6bc46d40..2cd239e2fcf 100644 --- a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java +++ b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java @@ -159,4 +159,12 @@ public class CombinedBiometricStatusUtils { public String getProfileSettingsClassName() { return Settings.CombinedBiometricProfileSettingsActivity.class.getName(); } + + /** + * Returns the class name of the Settings page corresponding to combined biometric settings for + * Private profile. + */ + public String getPrivateProfileSettingsClassName() { + return Settings.PrivateSpaceBiometricSettingsActivity.class.getName(); + } } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index d68f2c809c3..69a9fb36619 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -165,6 +165,7 @@ import com.android.settings.print.PrintJobSettingsFragment; import com.android.settings.print.PrintSettingsFragment; import com.android.settings.privacy.PrivacyControlsFragment; import com.android.settings.privacy.PrivacyDashboardFragment; +import com.android.settings.privatespace.onelock.PrivateSpaceBiometricSettings; import com.android.settings.regionalpreferences.RegionalPreferencesEntriesFragment; import com.android.settings.safetycenter.MoreSecurityPrivacyFragment; import com.android.settings.security.LockscreenDashboardFragment; @@ -264,6 +265,7 @@ public class SettingsGateway { FingerprintSettingsV2Fragment.class.getName(), CombinedBiometricSettings.class.getName(), CombinedBiometricProfileSettings.class.getName(), + PrivateSpaceBiometricSettings.class.getName(), SwipeToNotificationSettings.class.getName(), DoubleTapPowerSettings.class.getName(), DoubleTapScreenSettings.class.getName(), diff --git a/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java b/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java index e130e4d0508..271a2195d30 100644 --- a/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java +++ b/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java @@ -17,25 +17,43 @@ package com.android.settings.privatespace.onelock; import android.content.Context; -import android.text.TextUtils; +import android.os.UserHandle; +import android.util.Log; import androidx.preference.Preference; import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; -import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settings.biometrics.combination.CombinedBiometricStatusPreferenceController; +import com.android.settings.privatespace.PrivateSpaceMaintainer; +import com.android.settingslib.core.lifecycle.Lifecycle; /** Represents the preference controller to enroll biometrics for private space lock. */ -public class FaceFingerprintUnlockController extends AbstractPreferenceController { +public class FaceFingerprintUnlockController extends CombinedBiometricStatusPreferenceController { + private static final String TAG = "PSBiometricCtrl"; private static final String KEY_SET_UNSET_FACE_FINGERPRINT = "private_space_biometrics"; + private final int mProfileUserId; - public FaceFingerprintUnlockController(Context context, SettingsPreferenceFragment host) { - super(context); + public FaceFingerprintUnlockController(Context context, Lifecycle lifecycle) { + super(context, KEY_SET_UNSET_FACE_FINGERPRINT, lifecycle); + mProfileUserId = getUserId(); + } + + protected boolean isUserSupported() { + return android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() + && mProfileUserId != UserHandle.USER_NULL; } @Override - public boolean isAvailable() { - return android.os.Flags.allowPrivateProfile(); + protected int getUserId() { + UserHandle privateProfileHandle = + PrivateSpaceMaintainer.getInstance(mContext).getPrivateProfileHandle(); + if (privateProfileHandle != null) { + return privateProfileHandle.getIdentifier(); + } else { + Log.e(TAG, "Private profile user handle is not expected to be null."); + } + return UserHandle.USER_NULL; } @Override @@ -44,14 +62,19 @@ public class FaceFingerprintUnlockController extends AbstractPreferenceControlle } @Override - public boolean handlePreferenceTreeClick(Preference preference) { - return TextUtils.equals(preference.getKey(), getPreferenceKey()); + protected String getSettingsClassName() { + return mCombinedBiometricStatusUtils.getPrivateProfileSettingsClassName(); } @Override public void updateState(Preference preference) { - //TODO(b/308862923) : Add condition to check and enable when separate private lock is set. - preference.setSummary(mContext.getString(R.string.lock_settings_profile_unified_summary)); - preference.setEnabled(false); + if (mLockPatternUtils.isSeparateProfileChallengeEnabled(mProfileUserId)) { + super.updateState(preference); + preference.setEnabled(true); + } else { + preference.setSummary( + mContext.getString(R.string.lock_settings_profile_unified_summary)); + preference.setEnabled(false); + } } } diff --git a/src/com/android/settings/privatespace/onelock/PrivateSpaceBiometricSettings.java b/src/com/android/settings/privatespace/onelock/PrivateSpaceBiometricSettings.java new file mode 100644 index 00000000000..dc00885cf87 --- /dev/null +++ b/src/com/android/settings/privatespace/onelock/PrivateSpaceBiometricSettings.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 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.privatespace.onelock; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.os.UserHandle; +import android.util.Log; + +import com.android.settings.R; +import com.android.settings.biometrics.combination.BiometricsSettingsBase; +import com.android.settings.privatespace.PrivateSpaceMaintainer; + +public class PrivateSpaceBiometricSettings extends BiometricsSettingsBase { + private static final String TAG = "PSBiometricSettings"; + private static final String KEY_FACE_SETTINGS = "private_space_face_unlock_settings"; + private static final String KEY_FINGERPRINT_SETTINGS = + "private_space_fingerprint_unlock_settings"; + + @Override + public void onAttach(Context context) { + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) { + super.onAttach(context); + UserHandle privateProfileHandle = + PrivateSpaceMaintainer.getInstance(context).getPrivateProfileHandle(); + if (privateProfileHandle != null) { + mUserId = privateProfileHandle.getIdentifier(); + } else { + mUserId = -1; + Log.e(TAG, "Private profile user handle is not expected to be null."); + } + } + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.private_space_biometric_settings; + } + + @Override + public String getFacePreferenceKey() { + return KEY_FACE_SETTINGS; + } + + @Override + public String getFingerprintPreferenceKey() { + return KEY_FINGERPRINT_SETTINGS; + } + + @Override + public String getUnlockPhonePreferenceKey() { + return ""; + } + + @Override + public String getUseInAppsPreferenceKey() { + return ""; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.PRIVATE_SPACE_SETTINGS; + } +} diff --git a/src/com/android/settings/privatespace/onelock/PrivateSpaceFacePreferenceController.java b/src/com/android/settings/privatespace/onelock/PrivateSpaceFacePreferenceController.java new file mode 100644 index 00000000000..cc22b87a0ec --- /dev/null +++ b/src/com/android/settings/privatespace/onelock/PrivateSpaceFacePreferenceController.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 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.privatespace.onelock; + +import android.content.Context; +import android.os.UserHandle; +import android.util.Log; + +import androidx.lifecycle.Lifecycle; + +import com.android.settings.biometrics.combination.BiometricFaceStatusPreferenceController; +import com.android.settings.privatespace.PrivateSpaceMaintainer; + +public class PrivateSpaceFacePreferenceController extends BiometricFaceStatusPreferenceController { + private static final String TAG = "PrivateSpaceFaceCtrl"; + + public PrivateSpaceFacePreferenceController(Context context, String key) { + super(context, key); + } + + public PrivateSpaceFacePreferenceController(Context context, String key, Lifecycle lifecycle) { + super(context, key, lifecycle); + } + + @Override + protected boolean isUserSupported() { + return android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() + && getUserId() != UserHandle.USER_NULL; + } + + @Override + protected int getUserId() { + UserHandle privateProfileHandle = + PrivateSpaceMaintainer.getInstance(mContext).getPrivateProfileHandle(); + if (privateProfileHandle != null) { + return privateProfileHandle.getIdentifier(); + } else { + Log.e(TAG, "Private profile user handle is not expected to be null."); + } + return UserHandle.USER_NULL; + } + + @Override + public int getAvailabilityStatus() { + return android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() + ? AVAILABLE + : UNSUPPORTED_ON_DEVICE; + } +} diff --git a/src/com/android/settings/privatespace/onelock/PrivateSpaceFingerprintPreferenceController.java b/src/com/android/settings/privatespace/onelock/PrivateSpaceFingerprintPreferenceController.java new file mode 100644 index 00000000000..f2f08017421 --- /dev/null +++ b/src/com/android/settings/privatespace/onelock/PrivateSpaceFingerprintPreferenceController.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 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.privatespace.onelock; + +import android.content.Context; +import android.os.UserHandle; +import android.util.Log; + +import androidx.lifecycle.Lifecycle; + +import com.android.settings.biometrics.combination.BiometricFingerprintStatusPreferenceController; +import com.android.settings.privatespace.PrivateSpaceMaintainer; + +public class PrivateSpaceFingerprintPreferenceController + extends BiometricFingerprintStatusPreferenceController { + private static final String TAG = "PrivateSpaceFingerCtrl"; + + public PrivateSpaceFingerprintPreferenceController(Context context, String key) { + super(context, key); + } + + public PrivateSpaceFingerprintPreferenceController( + Context context, String key, Lifecycle lifecycle) { + super(context, key, lifecycle); + } + + @Override + protected boolean isUserSupported() { + return android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() + && getUserId() != UserHandle.USER_NULL; + } + + @Override + protected int getUserId() { + UserHandle privateProfileHandle = + PrivateSpaceMaintainer.getInstance(mContext).getPrivateProfileHandle(); + if (privateProfileHandle != null) { + return privateProfileHandle.getIdentifier(); + } else { + Log.e(TAG, "Private profile user handle is not expected to be null."); + } + return UserHandle.USER_NULL; + } + + @Override + public int getAvailabilityStatus() { + return android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() + ? AVAILABLE + : UNSUPPORTED_ON_DEVICE; + } +} diff --git a/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java b/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java index 54b0374676f..fd7d02b6c7a 100644 --- a/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java +++ b/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java @@ -31,6 +31,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Log; +import androidx.annotation.Nullable; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -122,15 +123,15 @@ public class UseOneLockControllerSwitch extends AbstractPreferenceController } /** Method to handle onActivityResult */ - public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { + public boolean handleActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (requestCode == UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST - && resultCode == Activity.RESULT_OK) { + && resultCode == Activity.RESULT_OK && data != null) { mCurrentDevicePassword = data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); separateLocks(); return true; } else if (requestCode == UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST - && resultCode == Activity.RESULT_OK) { + && resultCode == Activity.RESULT_OK && data != null) { mCurrentProfilePassword = data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); unifyLocks(); diff --git a/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java b/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java index 181a8d0ea44..6af6c38174f 100644 --- a/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java +++ b/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java @@ -71,14 +71,14 @@ public class UseOneLockSettingsFragment extends DashboardFragment { final List controllers = new ArrayList<>(); controllers.add(new UseOneLockControllerSwitch(context, this)); controllers.add(new PrivateSpaceLockController(context, this)); - controllers.add(new FaceFingerprintUnlockController(context, this)); + controllers.add(new FaceFingerprintUnlockController(context, getSettingsLifecycle())); return controllers; } @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (use(UseOneLockControllerSwitch.class) - .handleActivityResult(requestCode, resultCode, data)) { + .handleActivityResult(requestCode, resultCode, data)) { return; } super.onActivityResult(requestCode, resultCode, data); diff --git a/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java index 706aedad074..a671a45b448 100644 --- a/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java +++ b/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java @@ -34,6 +34,7 @@ import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -44,6 +45,7 @@ import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.utils.StringUtil; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -71,6 +73,8 @@ public class CombinedBiometricStatusUtilsTest { private Context mApplicationContext; private CombinedBiometricStatusUtils mCombinedBiometricStatusUtils; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void setUp() { @@ -301,6 +305,17 @@ public class CombinedBiometricStatusUtilsTest { .isEqualTo(Settings.CombinedBiometricProfileSettingsActivity.class.getName()); } + @Test + public void getPrivateProfileSettingsClassName_returnsPrivateSpaceBiometricSettings() { + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); + mSetFlagsRule.enableFlags( + android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + + assertThat(mCombinedBiometricStatusUtils.getPrivateProfileSettingsClassName()) + .isEqualTo(Settings.PrivateSpaceBiometricSettingsActivity.class.getName()); + } + private List createFingerprintList(int size) { final List fingerprintList = new ArrayList<>(size); for (int i = 0; i < size; i++) { diff --git a/tests/unit/src/com/android/settings/privatespace/FaceFingerprintUnlockControllerTest.java b/tests/unit/src/com/android/settings/privatespace/FaceFingerprintUnlockControllerTest.java new file mode 100644 index 00000000000..f1da363b5e9 --- /dev/null +++ b/tests/unit/src/com/android/settings/privatespace/FaceFingerprintUnlockControllerTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2023 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.privatespace; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.preference.Preference; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.privatespace.onelock.FaceFingerprintUnlockController; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class FaceFingerprintUnlockControllerTest { + @Mock private Context mContext; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Mock Lifecycle mLifecycle; + @Mock LockPatternUtils mLockPatternUtils; + + private Preference mPreference; + private FaceFingerprintUnlockController mFaceFingerprintUnlockController; + + /** Required setup before a test. */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + final String preferenceKey = "private_space_biometrics"; + + mPreference = new Preference(ApplicationProvider.getApplicationContext()); + mPreference.setKey(preferenceKey); + + final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); + when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) + .thenReturn(mLockPatternUtils); + + mFaceFingerprintUnlockController = + new FaceFingerprintUnlockController(mContext, mLifecycle); + } + + /** Tests that the controller is always available. */ + @Test + public void getAvailabilityStatus_whenFlagsEnabled_returnsAvailable() { + mSetFlagsRule.enableFlags( + android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + + assertThat(mFaceFingerprintUnlockController.isAvailable()).isEqualTo(true); + } + + /** Tests that the controller is not available when Biometrics flag is not enabled. */ + @Test + public void getAvailabilityStatus_whenBiometricFlagDisabled_returnsFalse() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.disableFlags( + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + + assertThat(mFaceFingerprintUnlockController.isAvailable()).isEqualTo(false); + } + + /** Tests that the controller is not available when private feature flag is not enabled. */ + @Test + public void getAvailabilityStatus_whenPrivateFlagDisabled_returnsFalse() { + mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + + assertThat(mFaceFingerprintUnlockController.isAvailable()).isEqualTo(false); + } + + /** Tests that preference is disabled and summary says same as device lock. */ + @Test + public void getSummary_whenScreenLock() { + doReturn(false).when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt()); + mSetFlagsRule.enableFlags( + android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + + mFaceFingerprintUnlockController.updateState(mPreference); + assertThat(mPreference.isEnabled()).isFalse(); + assertThat(mPreference.getSummary().toString()).isEqualTo("Same as device screen lock"); + } + + /** Tests that preference is enabled and summary is not same as device lock. */ + @Test + public void getSummary_whenSeparateProfileLock() { + doReturn(true).when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt()); + mSetFlagsRule.enableFlags( + android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + + mFaceFingerprintUnlockController.updateState(mPreference); + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.getSummary().toString()).isNotEqualTo("Same as device screen lock"); + } +}