diff --git a/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java b/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java index f61f99c93f0..76a23a5f059 100644 --- a/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java @@ -25,6 +25,7 @@ import androidx.preference.Preference; import com.android.internal.widget.LockPatternUtils; import com.android.settings.Utils; +import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; import com.android.settings.core.BasePreferenceController; import com.android.settings.overlay.FeatureFactory; @@ -37,11 +38,17 @@ public abstract class BiometricStatusPreferenceController extends BasePreference protected final int mProfileChallengeUserId; private final BiometricNavigationUtils mBiometricNavigationUtils; + private final ActiveUnlockStatusUtils mActiveUnlockStatusUtils; + + /** + * @return true if the controller should be shown exclusively. + */ + protected abstract boolean isDeviceSupported(); /** * @return true if the manager is not null and the hardware is detected. */ - protected abstract boolean isDeviceSupported(); + protected abstract boolean isHardwareSupported(); /** * @return the summary text. @@ -61,13 +68,21 @@ public abstract class BiometricStatusPreferenceController extends BasePreference .getLockPatternUtils(context); mProfileChallengeUserId = Utils.getManagedProfileId(mUm, mUserId); mBiometricNavigationUtils = new BiometricNavigationUtils(getUserId()); + mActiveUnlockStatusUtils = new ActiveUnlockStatusUtils(context); } @Override public int getAvailabilityStatus() { + if (mActiveUnlockStatusUtils.isAvailable()) { + return getAvailabilityStatusWithWorkProfileCheck(); + } if (!isDeviceSupported()) { return UNSUPPORTED_ON_DEVICE; } + return getAvailabilityFromUserSupported(); + } + + private int getAvailabilityFromUserSupported() { if (isUserSupported()) { return AVAILABLE; } else { @@ -75,6 +90,21 @@ public abstract class BiometricStatusPreferenceController extends BasePreference } } + // Since this code is flag guarded by mActiveUnlockStatusUtils.isAvailable(), we don't need to + // do another check here. + private int getAvailabilityStatusWithWorkProfileCheck() { + if (!isHardwareSupported()) { + // no hardware, never show + return UNSUPPORTED_ON_DEVICE; + } + if (!isDeviceSupported() && isWorkProfileController()) { + // hardware supported but work profile, don't show + return UNSUPPORTED_ON_DEVICE; + } + // hardware supported, not work profile, active unlock enabled + return getAvailabilityFromUserSupported(); + } + @Override public void updateState(Preference preference) { if (!isAvailable()) { @@ -105,4 +135,11 @@ public abstract class BiometricStatusPreferenceController extends BasePreference protected boolean isUserSupported() { return true; } + + /** + * Returns true if the controller controls is used for work profile. + */ + protected boolean isWorkProfileController() { + return false; + } } diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java new file mode 100644 index 00000000000..640f08de512 --- /dev/null +++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java @@ -0,0 +1,170 @@ +/* + * 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.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.provider.DeviceConfig; +import android.provider.Settings; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settings.Utils; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.BasePreferenceController.AvailabilityStatus; + +import java.util.List; + +/** Utilities for active unlock details shared between Security Settings and Safety Center. */ +public class ActiveUnlockStatusUtils { + + /** The flag to determining whether active unlock in settings is enabled. */ + public static final String CONFIG_FLAG_NAME = "active_unlock_in_settings"; + + /** Flag value that represents the layout for unlock intent should be used. */ + public static final String UNLOCK_INTENT_LAYOUT = "unlock_intent_layout"; + + /** Flag value that represents the layout for biometric failure should be used. */ + public static final String BIOMETRIC_FAILURE_LAYOUT = "biometric_failure_layout"; + + private static final String ACTIVE_UNLOCK_PROVIDER = "active_unlock_provider"; + private static final String ACTIVE_UNLOCK_TARGET = "active_unlock_target"; + + private static final String TAG = "ActiveUnlockStatusUtils"; + + private final Context mContext; + private final ContentResolver mContentResolver; + + public ActiveUnlockStatusUtils(@NonNull Context context) { + mContext = context; + mContentResolver = mContext.getContentResolver(); + } + + /** Returns whether the active unlock settings entity should be shown. */ + public boolean isAvailable() { + return getAvailability() == BasePreferenceController.AVAILABLE; + } + + /** + * Returns whether the active unlock layout with the unlock on intent configuration should be + * used. + */ + public boolean useUnlockIntentLayout() { + return isAvailable() && UNLOCK_INTENT_LAYOUT.equals(getFlagState()); + } + + /** + * + * Returns whether the active unlock layout with the unlock on biometric failure configuration + * should be used. + */ + public boolean useBiometricFailureLayout() { + return isAvailable() && BIOMETRIC_FAILURE_LAYOUT.equals(getFlagState()); + } + + /** + * Returns the authority used to fetch dynamic active unlock content. + */ + @Nullable + public String getAuthority() { + final String authority = Settings.Secure.getString( + mContext.getContentResolver(), ACTIVE_UNLOCK_PROVIDER); + if (authority == null) { + Log.i(TAG, "authority not set"); + return null; + } + final List packageInfos = + mContext.getPackageManager().getInstalledPackages( + PackageManager.PackageInfoFlags.of(PackageManager.GET_PROVIDERS)); + for (PackageInfo packageInfo : packageInfos) { + final ProviderInfo[] providers = packageInfo.providers; + if (providers != null) { + for (ProviderInfo provider : providers) { + if (authority.equals(provider.authority) && isSystemApp(provider)) { + return authority; + } + } + } + } + Log.e(TAG, "authority not valid"); + return null; + } + + private static boolean isSystemApp(ComponentInfo componentInfo) { + final ApplicationInfo applicationInfo = componentInfo.applicationInfo; + if (applicationInfo == null) { + Log.e(TAG, "application info is null"); + return false; + } + return applicationInfo.isSystemApp(); + } + + /** + * Returns the intent used to launch the active unlock activity. + */ + @Nullable + public Intent getIntent() { + final String targetAction = Settings.Secure.getString( + mContentResolver, ACTIVE_UNLOCK_TARGET); + if (targetAction == null) { + Log.i(TAG, "Target action not set"); + return null; + } + final Intent intent = new Intent(targetAction); + final ActivityInfo activityInfo = intent.resolveActivityInfo( + mContext.getPackageManager(), PackageManager.MATCH_ALL); + if (activityInfo == null) { + Log.e(TAG, "Target activity not found"); + return null; + } + if (!isSystemApp(activityInfo)) { + Log.e(TAG, "Target application is not system"); + return null; + } + Log.i(TAG, "Target application is valid"); + return intent; + } + + /** Returns the availability status of the active unlock feature. */ + @AvailabilityStatus + int getAvailability() { + if (!Utils.hasFingerprintHardware(mContext) && !Utils.hasFaceHardware(mContext)) { + return BasePreferenceController.UNSUPPORTED_ON_DEVICE; + } + if (!UNLOCK_INTENT_LAYOUT.equals(getFlagState()) + && !BIOMETRIC_FAILURE_LAYOUT.equals(getFlagState())) { + return BasePreferenceController.CONDITIONALLY_UNAVAILABLE; + } + if (getAuthority() != null && getIntent() != null) { + return BasePreferenceController.AVAILABLE; + } + return BasePreferenceController.CONDITIONALLY_UNAVAILABLE; + } + + private static String getFlagState() { + return DeviceConfig.getProperty(DeviceConfig.NAMESPACE_REMOTE_AUTH, CONFIG_FLAG_NAME); + } +} diff --git a/src/com/android/settings/biometrics/combination/BiometricFaceProfileStatusPreferenceController.java b/src/com/android/settings/biometrics/combination/BiometricFaceProfileStatusPreferenceController.java index de021261b30..c21368bff91 100644 --- a/src/com/android/settings/biometrics/combination/BiometricFaceProfileStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/combination/BiometricFaceProfileStatusPreferenceController.java @@ -46,4 +46,9 @@ public class BiometricFaceProfileStatusPreferenceController extends protected int getUserId() { return mProfileChallengeUserId; } + + @Override + protected boolean isWorkProfileController() { + return true; + } } diff --git a/src/com/android/settings/biometrics/combination/BiometricFaceStatusPreferenceController.java b/src/com/android/settings/biometrics/combination/BiometricFaceStatusPreferenceController.java index 800139ce3f7..c9ea9449c7e 100644 --- a/src/com/android/settings/biometrics/combination/BiometricFaceStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/combination/BiometricFaceStatusPreferenceController.java @@ -39,6 +39,11 @@ public class BiometricFaceStatusPreferenceController extends FaceStatusPreferenc @Override protected boolean isDeviceSupported() { - return Utils.isMultipleBiometricsSupported(mContext) && Utils.hasFaceHardware(mContext); + return Utils.isMultipleBiometricsSupported(mContext) && isHardwareSupported(); + } + + @Override + protected boolean isHardwareSupported() { + return Utils.hasFaceHardware(mContext); } } diff --git a/src/com/android/settings/biometrics/combination/BiometricFingerprintProfileStatusPreferenceController.java b/src/com/android/settings/biometrics/combination/BiometricFingerprintProfileStatusPreferenceController.java index 0c50230c989..52e44318daa 100644 --- a/src/com/android/settings/biometrics/combination/BiometricFingerprintProfileStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/combination/BiometricFingerprintProfileStatusPreferenceController.java @@ -46,4 +46,9 @@ public class BiometricFingerprintProfileStatusPreferenceController extends protected int getUserId() { return mProfileChallengeUserId; } + + @Override + protected boolean isWorkProfileController() { + return true; + } } diff --git a/src/com/android/settings/biometrics/combination/BiometricFingerprintStatusPreferenceController.java b/src/com/android/settings/biometrics/combination/BiometricFingerprintStatusPreferenceController.java index be19cb5cada..9789417acee 100644 --- a/src/com/android/settings/biometrics/combination/BiometricFingerprintStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/combination/BiometricFingerprintStatusPreferenceController.java @@ -40,7 +40,11 @@ public class BiometricFingerprintStatusPreferenceController extends @Override protected boolean isDeviceSupported() { - return Utils.isMultipleBiometricsSupported(mContext) - && Utils.hasFingerprintHardware(mContext); + return Utils.isMultipleBiometricsSupported(mContext) && isHardwareSupported(); + } + + @Override + protected boolean isHardwareSupported() { + return Utils.hasFingerprintHardware(mContext); } } diff --git a/src/com/android/settings/biometrics/combination/BiometricSettingsAppPreferenceController.java b/src/com/android/settings/biometrics/combination/BiometricSettingsAppPreferenceController.java index a46ae7a728c..6153a1a1907 100644 --- a/src/com/android/settings/biometrics/combination/BiometricSettingsAppPreferenceController.java +++ b/src/com/android/settings/biometrics/combination/BiometricSettingsAppPreferenceController.java @@ -24,6 +24,7 @@ import android.hardware.fingerprint.FingerprintManager; import android.provider.Settings; import com.android.settings.Utils; +import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; import com.android.settings.core.TogglePreferenceController; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; @@ -69,7 +70,10 @@ public class BiometricSettingsAppPreferenceController extends TogglePreferenceCo @Override public int getAvailabilityStatus() { - if (!Utils.isMultipleBiometricsSupported(mContext)) { + final ActiveUnlockStatusUtils activeUnlockStatusUtils = + new ActiveUnlockStatusUtils(mContext); + if (!Utils.isMultipleBiometricsSupported(mContext) + && !activeUnlockStatusUtils.isAvailable()) { return UNSUPPORTED_ON_DEVICE; } if (mFaceManager == null || mFingerprintManager == null) { diff --git a/src/com/android/settings/biometrics/combination/BiometricSettingsKeyguardPreferenceController.java b/src/com/android/settings/biometrics/combination/BiometricSettingsKeyguardPreferenceController.java index 2d2255805cc..cfd220e87a4 100644 --- a/src/com/android/settings/biometrics/combination/BiometricSettingsKeyguardPreferenceController.java +++ b/src/com/android/settings/biometrics/combination/BiometricSettingsKeyguardPreferenceController.java @@ -22,6 +22,7 @@ import android.content.Context; import android.provider.Settings; import com.android.settings.Utils; +import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; import com.android.settings.core.TogglePreferenceController; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; @@ -63,9 +64,18 @@ public class BiometricSettingsKeyguardPreferenceController extends TogglePrefere @Override public int getAvailabilityStatus() { + final ActiveUnlockStatusUtils activeUnlockStatusUtils = + new ActiveUnlockStatusUtils(mContext); + if (activeUnlockStatusUtils.isAvailable()) { + return getAvailabilityFromRestrictingAdmin(); + } if (!Utils.isMultipleBiometricsSupported(mContext)) { return UNSUPPORTED_ON_DEVICE; } + return getAvailabilityFromRestrictingAdmin(); + } + + private int getAvailabilityFromRestrictingAdmin() { return getRestrictingAdmin() != null ? DISABLED_FOR_USER : AVAILABLE; } diff --git a/src/com/android/settings/biometrics/combination/CombinedBiometricProfileStatusPreferenceController.java b/src/com/android/settings/biometrics/combination/CombinedBiometricProfileStatusPreferenceController.java index b8706a553d3..67c267d43be 100644 --- a/src/com/android/settings/biometrics/combination/CombinedBiometricProfileStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/combination/CombinedBiometricProfileStatusPreferenceController.java @@ -62,4 +62,9 @@ public class CombinedBiometricProfileStatusPreferenceController extends protected String getSettingsClassName() { return mCombinedBiometricStatusUtils.getProfileSettingsClassName(); } + + @Override + protected boolean isWorkProfileController() { + return true; + } } diff --git a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java index 50eb43d6545..a337c3b22ee 100644 --- a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java @@ -25,6 +25,7 @@ import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.Utils; import com.android.settings.biometrics.BiometricStatusPreferenceController; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedPreference; @@ -84,6 +85,11 @@ public class CombinedBiometricStatusPreferenceController extends return mCombinedBiometricStatusUtils.isAvailable(); } + @Override + protected boolean isHardwareSupported() { + return Utils.hasFaceHardware(mContext) || Utils.hasFingerprintHardware(mContext); + } + @Override public void updateState(Preference preference) { super.updateState(preference); diff --git a/src/com/android/settings/biometrics/face/FaceProfileStatusPreferenceController.java b/src/com/android/settings/biometrics/face/FaceProfileStatusPreferenceController.java index a2e11afb7e0..122138947d4 100644 --- a/src/com/android/settings/biometrics/face/FaceProfileStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/face/FaceProfileStatusPreferenceController.java @@ -84,4 +84,9 @@ public class FaceProfileStatusPreferenceController extends FaceStatusPreferenceC mContext.getResources().getString( R.string.security_settings_face_profile_preference_title))); } + + @Override + protected boolean isWorkProfileController() { + return true; + } } diff --git a/src/com/android/settings/biometrics/face/FaceStatusPreferenceController.java b/src/com/android/settings/biometrics/face/FaceStatusPreferenceController.java index f18a74fa190..c71119cdeee 100644 --- a/src/com/android/settings/biometrics/face/FaceStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/face/FaceStatusPreferenceController.java @@ -86,6 +86,11 @@ public class FaceStatusPreferenceController extends BiometricStatusPreferenceCon return mFaceStatusUtils.isAvailable(); } + @Override + protected boolean isHardwareSupported() { + return Utils.hasFaceHardware(mContext); + } + @Override public void updateState(Preference preference) { super.updateState(preference); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintProfileStatusPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintProfileStatusPreferenceController.java index d6d0b8f8ac4..051d25498c2 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintProfileStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintProfileStatusPreferenceController.java @@ -53,4 +53,9 @@ public class FingerprintProfileStatusPreferenceController protected int getUserId() { return mProfileChallengeUserId; } + + @Override + protected boolean isWorkProfileController() { + return true; + } } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceController.java index 347fec72870..fba93e10b41 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceController.java @@ -86,6 +86,11 @@ public class FingerprintStatusPreferenceController extends BiometricStatusPrefer return mFingerprintStatusUtils.isAvailable(); } + @Override + protected boolean isHardwareSupported() { + return Utils.hasFingerprintHardware(mContext); + } + @Override public void updateState(Preference preference) { super.updateState(preference); diff --git a/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java b/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java new file mode 100644 index 00000000000..a2907f8a1bc --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java @@ -0,0 +1,138 @@ +/* + * 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.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; +import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; + +import static com.google.common.truth.Truth.assertThat; + +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 androidx.test.core.app.ApplicationProvider; + +import com.android.settings.testutils.ActiveUnlockTestUtils; +import com.android.settings.testutils.shadow.ShadowDeviceConfig; + +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.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowDeviceConfig.class}) +public class ActiveUnlockStatusUtilsTest { + + @Rule public final MockitoRule mMocks = MockitoJUnit.rule(); + + @Mock private PackageManager mPackageManager; + @Mock private FingerprintManager mFingerprintManager; + @Mock private FaceManager mFaceManager; + + private Context mApplicationContext; + private ActiveUnlockStatusUtils mActiveUnlockStatusUtils; + + @Before + public void setUp() { + mApplicationContext = spy(ApplicationProvider.getApplicationContext()); + when(mApplicationContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true); + when(mApplicationContext.getSystemService(Context.FINGERPRINT_SERVICE)) + .thenReturn(mFingerprintManager); + when(mApplicationContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + ActiveUnlockTestUtils.enable(mApplicationContext); + mActiveUnlockStatusUtils = new ActiveUnlockStatusUtils(mApplicationContext); + } + + @After + public void tearDown() { + ActiveUnlockTestUtils.disable(mApplicationContext); + } + + @Test + public void isAvailable_featureFlagDisabled_returnsConditionallyUnavailable() { + ActiveUnlockTestUtils.disable(mApplicationContext); + + assertThat(mActiveUnlockStatusUtils.getAvailability()).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void isAvailable_withoutFingerprint_withoutFace_returnsUnsupported() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + when(mFaceManager.isHardwareDetected()).thenReturn(false); + + assertThat(mActiveUnlockStatusUtils.getAvailability()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + @Test + public void isAvailable_withoutFingerprint_withFace_returnsAvailable() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + + assertThat(mActiveUnlockStatusUtils.getAvailability()).isEqualTo(AVAILABLE); + } + + @Test + public void isAvailable_withFingerprint_withoutFace_returnsAvailable() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(false); + + assertThat(mActiveUnlockStatusUtils.getAvailability()).isEqualTo(AVAILABLE); + } + + @Test + public void isAvailable_withFingerprint_withFace_returnsAvailable() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + + assertThat(mActiveUnlockStatusUtils.getAvailability()).isEqualTo(AVAILABLE); + } + + @Test + public void configIsUnlockOnIntent_useUnlockIntentLayoutIsTrue() { + ActiveUnlockTestUtils.enable( + mApplicationContext, ActiveUnlockStatusUtils.UNLOCK_INTENT_LAYOUT); + + assertThat(mActiveUnlockStatusUtils.useUnlockIntentLayout()).isTrue(); + assertThat(mActiveUnlockStatusUtils.useBiometricFailureLayout()).isFalse(); + } + + @Test + public void configIsBiometricFailure_useBiometricFailureLayoutIsTrue() { + ActiveUnlockTestUtils.enable( + mApplicationContext, ActiveUnlockStatusUtils.BIOMETRIC_FAILURE_LAYOUT); + + assertThat(mActiveUnlockStatusUtils.useUnlockIntentLayout()).isFalse(); + assertThat(mActiveUnlockStatusUtils.useBiometricFailureLayout()).isTrue(); + } +} diff --git a/tests/robotests/src/com/android/settings/biometrics/combination/BiometricFaceStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/combination/BiometricFaceStatusPreferenceControllerTest.java new file mode 100644 index 00000000000..84a9ad42acb --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/combination/BiometricFaceStatusPreferenceControllerTest.java @@ -0,0 +1,113 @@ +/* + * 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.combination; + +import static com.google.common.truth.Truth.assertThat; + +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 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 BiometricFaceStatusPreferenceControllerTest { + + @Rule public final MockitoRule mMocks = MockitoJUnit.rule(); + + @Mock private UserManager mUserManager; + @Mock private PackageManager mPackageManager; + @Mock private FingerprintManager mFingerprintManager; + @Mock private FaceManager mFaceManager; + + private Context mContext; + private RestrictedPreference mPreference; + private BiometricFaceStatusPreferenceController mController; + + @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); + mController = new BiometricFaceStatusPreferenceController(mContext, "preferenceKey"); + } + + @After + public void tearDown() { + ActiveUnlockTestUtils.disable(mContext); + } + + @Test + public void onlyFaceEnabled_preferenceNotVisible() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isVisible()).isFalse(); + } + + @Test + public void onlyFaceAndActiveUnlockEnabled_preferenceVisible() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + ActiveUnlockTestUtils.enable(mContext); + + mController.updateState(mPreference); + + assertThat(mPreference.isVisible()).isTrue(); + } + + @Test + public void faceAndFingerprintEnabled_preferenceVisible() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isVisible()).isTrue(); + } +} diff --git a/tests/robotests/src/com/android/settings/biometrics/combination/BiometricFingerprintStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/combination/BiometricFingerprintStatusPreferenceControllerTest.java new file mode 100644 index 00000000000..3eb4c21f6a2 --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/combination/BiometricFingerprintStatusPreferenceControllerTest.java @@ -0,0 +1,113 @@ +/* + * 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.combination; + +import static com.google.common.truth.Truth.assertThat; + +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 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 BiometricFingerprintStatusPreferenceControllerTest { + + @Rule public final MockitoRule mMocks = MockitoJUnit.rule(); + + @Mock private UserManager mUserManager; + @Mock private PackageManager mPackageManager; + @Mock private FingerprintManager mFingerprintManager; + @Mock private FaceManager mFaceManager; + + private Context mContext; + private RestrictedPreference mPreference; + private BiometricFingerprintStatusPreferenceController mController; + + @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); + mController = new BiometricFingerprintStatusPreferenceController(mContext, "preferenceKey"); + } + + @After + public void tearDown() { + ActiveUnlockTestUtils.disable(mContext); + } + + @Test + public void onlyFingerprintEnabled_preferenceNotVisible() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(false); + + mController.updateState(mPreference); + + assertThat(mPreference.isVisible()).isFalse(); + } + + @Test + public void onlyFingerprintAndActiveUnlockEnabled_preferenceVisible() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(false); + ActiveUnlockTestUtils.enable(mContext); + + mController.updateState(mPreference); + + assertThat(mPreference.isVisible()).isTrue(); + } + + @Test + public void faceAndFingerprintEnabled_preferenceVisible() { + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isVisible()).isTrue(); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/ActiveUnlockTestUtils.java b/tests/robotests/src/com/android/settings/testutils/ActiveUnlockTestUtils.java new file mode 100644 index 00000000000..0cecaee2939 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/ActiveUnlockTestUtils.java @@ -0,0 +1,88 @@ +/* + * 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.testutils; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.provider.DeviceConfig; +import android.provider.Settings; + +import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; + +import java.util.ArrayList; + +/** Utilities class to enable or disable the Active Unlock flag in tests. */ +public final class ActiveUnlockTestUtils { + + public static final String TARGET = "com.active.unlock.target"; + public static final String PROVIDER = "com.active.unlock.provider"; + public static final String TARGET_SETTING = "active_unlock_target"; + public static final String PROVIDER_SETTING = "active_unlock_provider"; + + public static void enable(Context context) { + ActiveUnlockTestUtils.enable(context, ActiveUnlockStatusUtils.UNLOCK_INTENT_LAYOUT); + } + + public static void enable(Context context, String flagValue) { + Settings.Secure.putString( + context.getContentResolver(), TARGET_SETTING, TARGET); + Settings.Secure.putString( + context.getContentResolver(), PROVIDER_SETTING, PROVIDER); + + PackageManager packageManager = context.getPackageManager(); + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.applicationInfo = applicationInfo; + when(packageManager.resolveActivity(any(), anyInt())).thenReturn(resolveInfo); + + PackageInfo packageInfo = new PackageInfo(); + packageInfo.applicationInfo = applicationInfo; + ProviderInfo providerInfo = new ProviderInfo(); + providerInfo.authority = PROVIDER; + providerInfo.applicationInfo = applicationInfo; + packageInfo.providers = new ProviderInfo[] { providerInfo }; + ArrayList packageInfos = new ArrayList<>(); + packageInfos.add(packageInfo); + when(packageManager.getInstalledPackages(any())).thenReturn(packageInfos); + + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_REMOTE_AUTH, + ActiveUnlockStatusUtils.CONFIG_FLAG_NAME, + flagValue, + false /* makeDefault */); + } + + public static void disable(Context context) { + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_REMOTE_AUTH, + ActiveUnlockStatusUtils.CONFIG_FLAG_NAME, + null /* value */, + false /* makeDefault */); + } +}