From d3d08609d332939830247423ca70aad0013d90dc Mon Sep 17 00:00:00 2001 From: Derek Jedral Date: Sun, 22 Jan 2023 19:20:29 -0800 Subject: [PATCH] Add Active Unlock tile under face & fingerprint This tile will eventually link to GmsCore. It only shows up for non-work profile face & fingerprint pages. Its visibility depends on both whether the corresponding GmsCore component is enabled, as well as the feature flag. Test: make RunSettingsRoboTests Test: manually flip flags, confirm tile shows in combined biometric page Bug: 264813301 Change-Id: Ieea53f00e46cfbfe87e3b31756f64f299b7d3174 --- res/values/strings.xml | 3 + .../security_settings_combined_biometric.xml | 8 +- ...ctiveUnlockStatusPreferenceController.java | 106 +++++++++++++ ...eUnlockStatusPreferenceControllerTest.java | 139 ++++++++++++++++++ 4 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 19fe25c3c57..cd1b9093173 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6776,6 +6776,9 @@ face, fingerprint, add fingerprint + + watch unlock, add watch unlock + dim screen, touchscreen, battery, smart brightness, dynamic brightness, Auto brightness diff --git a/res/xml/security_settings_combined_biometric.xml b/res/xml/security_settings_combined_biometric.xml index ef3a3fd1ff2..4476281440c 100644 --- a/res/xml/security_settings_combined_biometric.xml +++ b/res/xml/security_settings_combined_biometric.xml @@ -40,6 +40,12 @@ settings:keywords="@string/keywords_fingerprint_settings" settings:controller="com.android.settings.biometrics.combination.BiometricFingerprintStatusPreferenceController" /> + - \ No newline at end of file + diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusPreferenceController.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusPreferenceController.java new file mode 100644 index 00000000000..e423a880bfe --- /dev/null +++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusPreferenceController.java @@ -0,0 +1,106 @@ +/* + * 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.biometrics.activeunlock; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.OnLifecycleEvent; +import androidx.preference.PreferenceScreen; + +import com.android.settings.Utils; +import com.android.settings.biometrics.BiometricStatusPreferenceController; +import com.android.settingslib.RestrictedPreference; + +/** + * Preference controller for active unlock settings within the biometrics settings page, that + * controls the ability to unlock the phone with watch authentication. + */ +public class ActiveUnlockStatusPreferenceController + extends BiometricStatusPreferenceController implements LifecycleObserver { + /** + * Preference key. + * + * This must match the key found in security_settings_combined_biometric.xml + **/ + public static final String KEY_ACTIVE_UNLOCK_SETTINGS = "biometric_active_unlock_settings"; + @Nullable private RestrictedPreference mPreference; + @Nullable private PreferenceScreen mPreferenceScreen; + private final ActiveUnlockStatusUtils mActiveUnlockStatusUtils; + + public ActiveUnlockStatusPreferenceController(@NonNull Context context) { + this(context, KEY_ACTIVE_UNLOCK_SETTINGS); + } + + public ActiveUnlockStatusPreferenceController( + @NonNull Context context, @NonNull String key) { + super(context, key); + mActiveUnlockStatusUtils = new ActiveUnlockStatusUtils(context); + } + + /** Resets the preference reference on resume. */ + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + public void onResume() { + if (mPreferenceScreen != null) { + displayPreference(mPreferenceScreen); + } + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreferenceScreen = screen; + mPreference = screen.findPreference(mPreferenceKey); + updateState(mPreference); + } + + @Override + public int getAvailabilityStatus() { + return mActiveUnlockStatusUtils.getAvailability(); + } + + @Override + protected boolean isDeviceSupported() { + // This should never be called, as getAvailabilityStatus() will return the exact value. + // However, this is an abstract method in BiometricStatusPreferenceController, and so + // needs to be overridden. + return mActiveUnlockStatusUtils.isAvailable(); + } + + @Override + protected boolean isHardwareSupported() { + // This should never be called, as getAvailabilityStatus() will return the exact value. + // However, this is an abstract method in BiometricStatusPreferenceController, and so + // needs to be overridden. + return Utils.hasFaceHardware(mContext) || Utils.hasFingerprintHardware(mContext); + } + + @Override + protected String getSummaryText() { + // TODO(b/264812018): set the summary from the ContentProvider + return ""; + } + + @Override + protected String getSettingsClassName() { + // TODO(b/264813445): direct user to face & fingerprint setup + return null; + } +} diff --git a/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusPreferenceControllerTest.java new file mode 100644 index 00000000000..572c005a67e --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusPreferenceControllerTest.java @@ -0,0 +1,139 @@ +/* + * 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.biometrics.activeunlock; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; +import android.os.UserManager; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.testutils.ActiveUnlockTestUtils; +import com.android.settings.testutils.shadow.ShadowDeviceConfig; +import com.android.settingslib.RestrictedPreference; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowDeviceConfig.class}) +public class ActiveUnlockStatusPreferenceControllerTest { + + @Rule public final MockitoRule mMocks = MockitoJUnit.rule(); + + @Mock private UserManager mUserManager; + @Mock private PackageManager mPackageManager; + @Mock private FingerprintManager mFingerprintManager; + @Mock private FaceManager mFaceManager; + @Mock private PreferenceScreen mPreferenceScreen; + + private Context mContext; + private ActiveUnlockStatusPreferenceController mController; + private RestrictedPreference mPreference; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true); + ShadowApplication.getInstance() + .setSystemService(Context.FINGERPRINT_SERVICE, mFingerprintManager); + ShadowApplication.getInstance().setSystemService(Context.FACE_SERVICE, mFaceManager); + ShadowApplication.getInstance().setSystemService(Context.USER_SERVICE, mUserManager); + when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[] {1234}); + mPreference = new RestrictedPreference(mContext); + when(mPreferenceScreen.findPreference(any())).thenReturn(mPreference); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + ActiveUnlockTestUtils.enable(mContext); + mController = new ActiveUnlockStatusPreferenceController(mContext); + } + + @After + public void tearDown() { + ActiveUnlockTestUtils.disable(mContext); + } + + @Test + public void updateState_featureFlagDisabled_isNotVisible() { + ActiveUnlockTestUtils.disable(mContext); + + mController.displayPreference(mPreferenceScreen); + + assertThat(mPreference.isVisible()).isFalse(); + } + + @Test + public void updateState_withoutFingerprint_withoutFace_isNotVisible() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + when(mFaceManager.isHardwareDetected()).thenReturn(false); + + mController.displayPreference(mPreferenceScreen); + + assertThat(mPreference.isVisible()).isFalse(); + } + + @Test + public void updateState_withoutFingerprint_withFace_isVisible() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + + mController.displayPreference(mPreferenceScreen); + + assertThat(mPreference.isVisible()).isTrue(); + } + + @Test + public void updateState_withFingerprint_withoutFace_isVisible() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(false); + + mController.displayPreference(mPreferenceScreen); + + assertThat(mPreference.isVisible()).isTrue(); + } + + @Test + public void updateState_withFingerprint_withFace_isVisible() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + + mController.displayPreference(mPreferenceScreen); + + assertThat(mPreference.isVisible()).isTrue(); + } +}