From 13d3bdc4c028ceaaad662b8e091355a1166e2317 Mon Sep 17 00:00:00 2001 From: Grace Cheng Date: Thu, 6 Oct 2022 11:44:26 +0000 Subject: [PATCH] =?UTF-8?q?Implement=20SFPS=20=E2=80=9Crequire=20screen=20?= =?UTF-8?q?on=20to=20auth=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates new setting on SFPS to require screen on before unlocking a device. Also sets up toggles for this setting at the end of fingerprint enrollment and on the fingerprint settings page, and adds tests to verify expected behavior. Test: make RunSettingsRoboTests ROBOTEST_FILTER=FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest Fixes: 249169615 Fixes: 245343077 Fixes: 248530806 Change-Id: Id588796426d071860b3cc2af9ec5798c0027c202 Merged-In: Ia44604b059c4847c40608419b2e16219976ced3e --- res/layout/sfps_enroll_finish_base.xml | 16 +- .../sfps_require_screen_on_to_auth_toggle.xml | 79 ++++++++++ res/values/strings.xml | 8 + res/xml/security_settings_fingerprint.xml | 17 +- .../fingerprint/FingerprintEnrollFinish.java | 34 ++++ ...ingerprintRequireScreenOnToAuthToggle.java | 77 +++++++++ .../fingerprint/FingerprintSettings.java | 91 ++++++++++- ...ngerprintSettingsPreferenceController.java | 60 +++++++ ...ireScreenOnToAuthPreferenceController.java | 102 ++++++++++++ ...creenOnToAuthPreferenceControllerTest.java | 149 ++++++++++++++++++ 10 files changed, 623 insertions(+), 10 deletions(-) create mode 100644 res/layout/sfps_require_screen_on_to_auth_toggle.xml create mode 100644 src/com/android/settings/biometrics/fingerprint/FingerprintRequireScreenOnToAuthToggle.java create mode 100644 src/com/android/settings/biometrics/fingerprint/FingerprintSettingsPreferenceController.java create mode 100644 src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest.java diff --git a/res/layout/sfps_enroll_finish_base.xml b/res/layout/sfps_enroll_finish_base.xml index 6e468c6472d..8d062d9409c 100644 --- a/res/layout/sfps_enroll_finish_base.xml +++ b/res/layout/sfps_enroll_finish_base.xml @@ -55,16 +55,20 @@ android:src="@drawable/sfps_enroll_finish" /> - - + + + android:layout_gravity="center_horizontal|bottom" /> - + \ No newline at end of file diff --git a/res/layout/sfps_require_screen_on_to_auth_toggle.xml b/res/layout/sfps_require_screen_on_to_auth_toggle.xml new file mode 100644 index 00000000000..929b64bb2c4 --- /dev/null +++ b/res/layout/sfps_require_screen_on_to_auth_toggle.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 42f8e4c0811..49b1ddd975a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1012,6 +1012,8 @@ Fingerprint + + When using Fingerprint Unlock Fingerprint for work @@ -1293,6 +1295,12 @@ Now you can use your fingerprint to unlock your device or verify it\u2019s you, like when you sign in to apps Now you can use your fingerprint to unlock your phone or verify it\u2019s you, like when you sign in to apps + + Unlock only when screen is on + + The screen must be on before you can unlock with your fingerprint. This makes accidental unlocking less likely. + + Screen, Unlock Do it later diff --git a/res/xml/security_settings_fingerprint.xml b/res/xml/security_settings_fingerprint.xml index 804ef881630..a4ce545a7ff 100644 --- a/res/xml/security_settings_fingerprint.xml +++ b/res/xml/security_settings_fingerprint.xml @@ -16,5 +16,20 @@ + xmlns:settings="http://schemas.android.com/apk/res-auto" + android:title="@string/security_settings_fingerprint_preference_title"> + + + + + + diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java index 0c7ef982408..45063104134 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java @@ -25,6 +25,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Bundle; import android.util.Log; import android.view.View; +import android.widget.CompoundButton; import androidx.annotation.VisibleForTesting; @@ -44,16 +45,24 @@ import java.util.List; public class FingerprintEnrollFinish extends BiometricEnrollBase { private static final String TAG = "FingerprintEnrollFinish"; + private static final String KEY_REQUIRE_SCREEN_ON_TO_AUTH = "require_screen_on_to_auth_toggle"; private static final String ACTION_FINGERPRINT_SETTINGS = "android.settings.FINGERPRINT_SETTINGS"; @VisibleForTesting static final String FINGERPRINT_SUGGESTION_ACTIVITY = "com.android.settings.SetupFingerprintSuggestionActivity"; + private FingerprintManager mFingerprintManager; + + private FingerprintSettingsRequireScreenOnToAuthPreferenceController + mRequireScreenOnToAuthPreferenceController; + private FingerprintRequireScreenOnToAuthToggle mRequireScreenOnToAuthToggle; private boolean mCanAssumeSfps; private boolean mIsAddAnotherOrFinish; + private CompoundButton.OnCheckedChangeListener mRequireScreenOnToAuthToggleListener; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -63,6 +72,11 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase { mCanAssumeSfps = props != null && props.size() == 1 && props.get(0).isAnySidefpsType(); if (mCanAssumeSfps) { setContentView(R.layout.sfps_enroll_finish); + mRequireScreenOnToAuthPreferenceController = + new FingerprintSettingsRequireScreenOnToAuthPreferenceController( + getApplicationContext(), + KEY_REQUIRE_SCREEN_ON_TO_AUTH + ); } else { setContentView(R.layout.fingerprint_enroll_finish); } @@ -90,6 +104,20 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase { .setTheme(R.style.SudGlifButton_Primary) .build() ); + + if (mCanAssumeSfps) { + mRequireScreenOnToAuthToggleListener = + (buttonView, isChecked) -> { + mRequireScreenOnToAuthPreferenceController.setChecked(isChecked); + }; + mRequireScreenOnToAuthToggle = findViewById(R.id.require_screen_on_to_auth_toggle); + mRequireScreenOnToAuthToggle.setChecked( + mRequireScreenOnToAuthPreferenceController.isChecked()); + mRequireScreenOnToAuthToggle.setListener(mRequireScreenOnToAuthToggleListener); + mRequireScreenOnToAuthToggle.setOnClickListener(v -> { + mRequireScreenOnToAuthToggle.getSwitch().toggle(); + }); + } } @Override @@ -103,6 +131,12 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase { @Override protected void onResume() { super.onResume(); + if (mCanAssumeSfps) { + mRequireScreenOnToAuthToggleListener.onCheckedChanged( + mRequireScreenOnToAuthToggle.getSwitch(), + mRequireScreenOnToAuthToggle.isChecked() + ); + } FooterButton addButton = mFooterBarMixin.getSecondaryButton(); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintRequireScreenOnToAuthToggle.java b/src/com/android/settings/biometrics/fingerprint/FingerprintRequireScreenOnToAuthToggle.java new file mode 100644 index 00000000000..f88c9aaec54 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintRequireScreenOnToAuthToggle.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 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.fingerprint; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.Switch; + +import com.android.settings.R; + +/** + * A layout that contains a start-justified title, and an end-justified switch. + */ +public class FingerprintRequireScreenOnToAuthToggle extends LinearLayout { + private Switch mSwitch; + + public FingerprintRequireScreenOnToAuthToggle(Context context) { + this(context, null /* attrs */); + } + + public FingerprintRequireScreenOnToAuthToggle(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FingerprintRequireScreenOnToAuthToggle( + Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + LayoutInflater.from(context).inflate(R.layout.sfps_require_screen_on_to_auth_toggle, + this, true /* attachToRoot */); + + mSwitch = findViewById(R.id.toggle); + mSwitch.setClickable(true); + mSwitch.setFocusable(false); + } + + public boolean isChecked() { + return mSwitch.isChecked(); + } + + /** + * + * @param checked + */ + public void setChecked(boolean checked) { + mSwitch.setChecked(checked); + } + + /** + * + * @param listener + */ + public void setListener(CompoundButton.OnCheckedChangeListener listener) { + mSwitch.setOnCheckedChangeListener(listener); + } + + public Switch getSwitch() { + return mSwitch; + } +} diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index c031fe646dd..1c7c891899e 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -56,21 +56,24 @@ import androidx.preference.Preference.OnPreferenceChangeListener; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; +import androidx.preference.SwitchPreference; import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; import com.android.settings.SubSettings; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricUtils; import com.android.settings.core.SettingsBaseActivity; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settings.dashboard.DashboardFragment; import com.android.settings.password.ChooseLockGeneric; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settingslib.HelpUtils; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; +import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.transition.SettingsTransitionHelper; import com.android.settingslib.widget.FooterPreference; import com.android.settingslib.widget.TwoTargetPreference; @@ -115,7 +118,26 @@ public class FingerprintSettings extends SubSettings { setTitle(msg); } - public static class FingerprintSettingsFragment extends SettingsPreferenceFragment + /** + * @param context + * @return true if the Fingerprint hardware is detected. + */ + public static boolean isFingerprintHardwareDetected(Context context) { + FingerprintManager manager = Utils.getFingerprintManagerOrNull(context); + boolean isHardwareDetected = false; + if (manager == null) { + Log.d(TAG, "FingerprintManager is null"); + } else { + isHardwareDetected = manager.isHardwareDetected(); + Log.d(TAG, "FingerprintManager is not null. Hardware detected: " + isHardwareDetected); + } + return manager != null && isHardwareDetected; + } + + /** + * + */ + public static class FingerprintSettingsFragment extends DashboardFragment implements OnPreferenceChangeListener, FingerprintPreference.OnDeleteClickListener { private static class FooterColumn { @@ -134,6 +156,8 @@ public class FingerprintSettings extends SubSettings { private static final String KEY_LAUNCHED_CONFIRM = "launched_confirm"; private static final String KEY_HAS_FIRST_ENROLLED = "has_first_enrolled"; private static final String KEY_IS_ENROLLING = "is_enrolled"; + private static final String KEY_REQUIRE_SCREEN_ON_TO_AUTH = + "security_settings_require_screen_on_to_auth"; private static final int MSG_REFRESH_FINGERPRINT_TEMPLATES = 1000; private static final int MSG_FINGER_AUTH_SUCCESS = 1001; @@ -149,6 +173,11 @@ public class FingerprintSettings extends SubSettings { protected static final boolean DEBUG = false; + private List mControllers; + private FingerprintSettingsRequireScreenOnToAuthPreferenceController + mRequireScreenOnToAuthPreferenceController; + private RestrictedSwitchPreference mRequireScreenOnToAuthPreference; + private FingerprintManager mFingerprintManager; private FingerprintUpdater mFingerprintUpdater; private List mSensorProperties; @@ -214,6 +243,7 @@ public class FingerprintSettings extends SubSettings { } private void updateDialog() { + setRequireScreenOnToAuthVisibility(); RenameDialog renameDialog = (RenameDialog) getFragmentManager(). findFragmentByTag(RenameDialog.class.getName()); if (renameDialog != null) { @@ -448,13 +478,36 @@ public class FingerprintSettings extends SubSettings { if (root != null) { root.removeAll(); } - addPreferencesFromResource(R.xml.security_settings_fingerprint); root = getPreferenceScreen(); addFingerprintItemPreferences(root); + addPreferencesFromResource(getPreferenceScreenResId()); + mRequireScreenOnToAuthPreference = findPreference(KEY_REQUIRE_SCREEN_ON_TO_AUTH); + for (AbstractPreferenceController controller : mControllers) { + ((FingerprintSettingsPreferenceController) controller).setUserId(mUserId); + } + mRequireScreenOnToAuthPreference.setChecked( + mRequireScreenOnToAuthPreferenceController.isChecked()); + mRequireScreenOnToAuthPreference.setOnPreferenceChangeListener( + (preference, newValue) -> { + boolean isChecked = ((SwitchPreference) preference).isChecked(); + mRequireScreenOnToAuthPreferenceController.setChecked(!isChecked); + return true; + }); setPreferenceScreen(root); return root; } + private void setRequireScreenOnToAuthVisibility() { + int fingerprintsEnrolled = mFingerprintManager.getEnrolledFingerprints(mUserId).size(); + final boolean removalInProgress = mRemovalSidecar.inProgress(); + // Removing last remaining fingerprint + if (fingerprintsEnrolled == 0 && removalInProgress) { + mRequireScreenOnToAuthPreference.setVisible(false); + } else { + mRequireScreenOnToAuthPreference.setVisible(true); + } + } + private void addFingerprintItemPreferences(PreferenceGroup root) { root.removeAll(); final List items = mFingerprintManager.getEnrolledFingerprints(mUserId); @@ -477,6 +530,7 @@ public class FingerprintSettings extends SubSettings { root.addPreference(pref); pref.setOnPreferenceChangeListener(this); } + Preference addPreference = new Preference(root.getContext()); addPreference.setKey(KEY_FINGERPRINT_ADD); addPreference.setTitle(R.string.fingerprint_add_title); @@ -568,6 +622,16 @@ public class FingerprintSettings extends SubSettings { } } + @Override + protected int getPreferenceScreenResId() { + return R.xml.security_settings_fingerprint; + } + + @Override + protected String getLogTag() { + return TAG; + } + @Override public void onSaveInstanceState(final Bundle outState) { outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, @@ -662,6 +726,27 @@ public class FingerprintSettings extends SubSettings { return R.string.help_url_fingerprint; } + @Override + protected List createPreferenceControllers(Context context) { + if (!isFingerprintHardwareDetected(context)) { + return null; + } + + mControllers = buildPreferenceControllers(context); + return mControllers; + } + + private List buildPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + mRequireScreenOnToAuthPreferenceController = + new FingerprintSettingsRequireScreenOnToAuthPreferenceController( + context, + KEY_REQUIRE_SCREEN_ON_TO_AUTH + ); + controllers.add(mRequireScreenOnToAuthPreferenceController); + return controllers; + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsPreferenceController.java new file mode 100644 index 00000000000..2ca5da8928e --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsPreferenceController.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 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.fingerprint; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; + +import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import com.android.settingslib.RestrictedLockUtilsInternal; + +/** + * Abstract base class for all fingerprint settings toggles. + */ +public abstract class FingerprintSettingsPreferenceController extends TogglePreferenceController { + + private int mUserId; + + public FingerprintSettingsPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + public void setUserId(int userId) { + mUserId = userId; + } + + protected int getUserId() { + return mUserId; + } + + protected EnforcedAdmin getRestrictingAdmin() { + return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( + mContext, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId); + } + + @Override + public final boolean isSliceable() { + return false; + } + + @Override + public int getSliceHighlightMenuRes() { + // not needed since it's not sliceable + return NO_RES; + } +} diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java new file mode 100644 index 00000000000..5b183c1433f --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 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.fingerprint; + +import android.content.Context; +import android.hardware.fingerprint.FingerprintManager; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.preference.Preference; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.Utils; + +/** + * Preference controller that controls whether a SFPS device is required to be interactive for + * fingerprint authentication to unlock the device. + */ +public class FingerprintSettingsRequireScreenOnToAuthPreferenceController + extends FingerprintSettingsPreferenceController { + private static final String TAG = + "FingerprintSettingsRequireScreenOnToAuthPreferenceController"; + + @VisibleForTesting + protected FingerprintManager mFingerprintManager; + + public FingerprintSettingsRequireScreenOnToAuthPreferenceController( + Context context, String prefKey) { + super(context, prefKey); + mFingerprintManager = Utils.getFingerprintManagerOrNull(context); + } + + @Override + public boolean isChecked() { + if (!FingerprintSettings.isFingerprintHardwareDetected(mContext)) { + return false; + } else if (getRestrictingAdmin() != null) { + return false; + } + int defaultValue = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0; + + return Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, + defaultValue, + getUserHandle()) != 0; + } + + @Override + public boolean setChecked(boolean isChecked) { + Settings.Secure.putIntForUser( + mContext.getContentResolver(), + Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, + isChecked ? 1 : 0, + getUserHandle()); + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + if (!FingerprintSettings.isFingerprintHardwareDetected(mContext)) { + preference.setEnabled(false); + } else if (!mFingerprintManager.hasEnrolledTemplates(getUserId())) { + preference.setEnabled(false); + } else { + preference.setEnabled(true); + } + } + + @Override + public int getAvailabilityStatus() { + if (mFingerprintManager != null + && mFingerprintManager.isHardwareDetected() + && mFingerprintManager.isPowerbuttonFps()) { + return mFingerprintManager.hasEnrolledTemplates(getUserId()) + ? AVAILABLE : DISABLED_DEPENDENT_SETTING; + } else { + return UNSUPPORTED_ON_DEVICE; + } + } + + private int getUserHandle() { + return UserHandle.of(getUserId()).getIdentifier(); + } + +} diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest.java new file mode 100644 index 00000000000..ff74d59782a --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2022 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.fingerprint; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING; +import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.fingerprint.FingerprintManager; +import android.provider.Settings; + +import com.android.settings.testutils.shadow.ShadowUtils; +import com.android.settingslib.RestrictedSwitchPreference; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowUtils.class}) +public class FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest { + + @Mock + private FingerprintManager mFingerprintManager; + @Mock + private PackageManager mPackageManager; + @Mock + private RestrictedSwitchPreference mPreference; + + private Context mContext; + private FingerprintSettingsRequireScreenOnToAuthPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(eq(Context.FINGERPRINT_SERVICE))).thenReturn( + mFingerprintManager); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + + mController = spy(new FingerprintSettingsRequireScreenOnToAuthPreferenceController(mContext, + "test_key")); + ReflectionHelpers.setField(mController, "mFingerprintManager", mFingerprintManager); + } + + @After + public void tearDown() { + ShadowUtils.reset(); + } + + @Test + public void onPreferenceChange_settingIsUpdated() { + boolean state = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, 1) != 0; + + assertThat(mController.isChecked()).isFalse(); + assertThat(mController.onPreferenceChange(mPreference, !state)).isTrue(); + boolean newState = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, 1) != 0; + assertThat(newState).isEqualTo(!state); + } + + @Test + public void isAvailable_isEnabled_whenSfpsHardwareDetected_AndHasEnrolledFingerprints() { + assertThat(mController.isAvailable()).isEqualTo(false); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + configure_hardwareDetected_isSfps_hasEnrolledTemplates( + true /* isHardwareDetected */, + true /* isPowerbuttonFps */, + true /* hasEnrolledTemplates */); + assertThat(mController.isAvailable()).isEqualTo(true); + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void isAvailable_isDisabled_whenSfpsHardwareDetected_AndNoEnrolledFingerprints() { + assertThat(mController.isAvailable()).isEqualTo(false); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + configure_hardwareDetected_isSfps_hasEnrolledTemplates( + true /* isHardwareDetected */, + true /* isPowerbuttonFps */, + false /* hasEnrolledTemplates */); + assertThat(mController.isAvailable()).isEqualTo(true); + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + public void isUnavailable_whenHardwareNotDetected() { + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + configure_hardwareDetected_isSfps_hasEnrolledTemplates( + false /* isHardwareDetected */, + true /* isPowerbuttonFps */, + true /* hasEnrolledTemplates */); + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + @Test + public void isUnavailable_onNonSfpsDevice() { + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + configure_hardwareDetected_isSfps_hasEnrolledTemplates( + true /* isHardwareDetected */, + false /* isPowerbuttonFps */, + true /* hasEnrolledTemplates */); + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + private void configure_hardwareDetected_isSfps_hasEnrolledTemplates( + boolean isHardwareDetected, boolean isPowerbuttonFps, boolean hasEnrolledTemplates) { + when(mFingerprintManager.isHardwareDetected()).thenReturn(isHardwareDetected); + when(mFingerprintManager.isPowerbuttonFps()).thenReturn(isPowerbuttonFps); + when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(hasEnrolledTemplates); + } + + +}