diff options
8 files changed, 798 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthResult.java b/services/core/java/com/android/server/biometrics/sensors/AuthResult.java new file mode 100644 index 000000000000..c0ebf6b8b634 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/AuthResult.java @@ -0,0 +1,40 @@ +/* + * 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.server.biometrics.sensors; + +import android.hardware.biometrics.BiometricManager; + +class AuthResult { + static final int FAILED = 0; + static final int LOCKED_OUT = 1; + static final int AUTHENTICATED = 2; + private final int mStatus; + private final int mBiometricStrength; + + AuthResult(int status, @BiometricManager.Authenticators.Types int strength) { + mStatus = status; + mBiometricStrength = strength; + } + + int getStatus() { + return mStatus; + } + + int getBiometricStrength() { + return mBiometricStrength; + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java new file mode 100644 index 000000000000..6d00c3fc15ea --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java @@ -0,0 +1,93 @@ +/* + * 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.server.biometrics.sensors; + +import android.hardware.biometrics.BiometricManager.Authenticators; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class that takes in a series of authentication attempts (successes, failures, lockouts) + * across different biometric strengths (convenience, weak, strong) and returns a single AuthResult. + * + * The AuthResult will be the strongest biometric operation that occurred amongst all reported + * operations, and if multiple such operations exist, it will favor a successful authentication. + */ +class AuthResultCoordinator { + + private static final String TAG = "AuthResultCoordinator"; + private final List<AuthResult> mOperations; + + AuthResultCoordinator() { + mOperations = new ArrayList<>(); + } + + /** + * Adds auth success for a given strength to the current operation list. + */ + void authenticatedFor(@Authenticators.Types int strength) { + mOperations.add(new AuthResult(AuthResult.AUTHENTICATED, strength)); + } + + /** + * Adds auth ended for a given strength to the current operation list. + */ + void authEndedFor(@Authenticators.Types int strength) { + mOperations.add(new AuthResult(AuthResult.FAILED, strength)); + } + + /** + * Adds a lock out of a given strength to the current operation list. + */ + void lockedOutFor(@Authenticators.Types int strength) { + mOperations.add(new AuthResult(AuthResult.LOCKED_OUT, strength)); + } + + /** + * Obtains an auth result & strength from a current set of biometric operations. + */ + AuthResult getResult() { + AuthResult result = new AuthResult(AuthResult.FAILED, Authenticators.BIOMETRIC_CONVENIENCE); + return mOperations.stream().filter( + (element) -> element.getStatus() != AuthResult.FAILED).reduce(result, + ((curr, next) -> { + int strengthCompare = curr.getBiometricStrength() - next.getBiometricStrength(); + if (strengthCompare < 0) { + return curr; + } else if (strengthCompare == 0) { + // Equal level of strength, favor authentication. + if (curr.getStatus() == AuthResult.AUTHENTICATED) { + return curr; + } else { + // Either next is Authenticated, or it is not, either way return this + // one. + return next; + } + } else { + // curr is a weaker biometric + return next; + } + })); + } + + void resetState() { + mOperations.clear(); + } +} + + diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java new file mode 100644 index 000000000000..13840ff389d9 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java @@ -0,0 +1,157 @@ +/* + * 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.server.biometrics.sensors; + +import android.hardware.biometrics.BiometricManager.Authenticators; +import android.util.Slog; + +import java.util.HashSet; +import java.util.Set; + +/** + * Coordinates lockout counter enforcement for all types of biometric strengths across all users. + * + * This class is not thread-safe. In general, all calls to this class should be made on the same + * handler to ensure no collisions. + */ +class AuthSessionCoordinator implements AuthSessionListener { + private static final String TAG = "AuthSessionCoordinator"; + + private final Set<Integer> mAuthOperations; + + private int mUserId; + private boolean mIsAuthenticating; + private AuthResultCoordinator mAuthResultCoordinator; + private MultiBiometricLockoutState mMultiBiometricLockoutState; + + AuthSessionCoordinator() { + mAuthOperations = new HashSet<>(); + mAuthResultCoordinator = new AuthResultCoordinator(); + mMultiBiometricLockoutState = new MultiBiometricLockoutState(); + } + + /** + * A Call indicating that an auth session has started + */ + void onAuthSessionStarted(int userId) { + mAuthOperations.clear(); + mUserId = userId; + mIsAuthenticating = true; + mAuthResultCoordinator.resetState(); + } + + /** + * Ends the current auth session and updates the lockout state. + * + * This can happen two ways. + * 1. Manually calling this API + * 2. If authStartedFor() was called, and all authentication attempts finish. + */ + void endAuthSession() { + if (mIsAuthenticating) { + mAuthOperations.clear(); + AuthResult res = + mAuthResultCoordinator.getResult(); + if (res.getStatus() == AuthResult.AUTHENTICATED) { + mMultiBiometricLockoutState.onUserUnlocked(mUserId, res.getBiometricStrength()); + } else if (res.getStatus() == AuthResult.LOCKED_OUT) { + mMultiBiometricLockoutState.onUserLocked(mUserId, res.getBiometricStrength()); + } + mAuthResultCoordinator.resetState(); + mIsAuthenticating = false; + } + } + + /** + * @return true if a user can authenticate with a given strength. + */ + boolean getCanAuthFor(int userId, @Authenticators.Types int strength) { + return mMultiBiometricLockoutState.canUserAuthenticate(userId, strength); + } + + @Override + public void authStartedFor(int userId, int sensorId) { + if (!mIsAuthenticating) { + onAuthSessionStarted(userId); + } + + if (mAuthOperations.contains(sensorId)) { + Slog.e(TAG, "Error, authStartedFor(" + sensorId + ") without being finished"); + return; + } + + if (mUserId != userId) { + Slog.e(TAG, "Error authStartedFor(" + userId + ") Incorrect userId, expected" + mUserId + + ", ignoring..."); + return; + } + + mAuthOperations.add(sensorId); + } + + @Override + public void authenticatedFor(int userId, @Authenticators.Types int biometricStrength, + int sensorId) { + mAuthResultCoordinator.authenticatedFor(biometricStrength); + attemptToFinish(userId, sensorId, + "authenticatedFor(userId=" + userId + ", biometricStrength=" + biometricStrength + + ", sensorId=" + sensorId + ""); + } + + @Override + public void lockedOutFor(int userId, @Authenticators.Types int biometricStrength, + int sensorId) { + mAuthResultCoordinator.lockedOutFor(biometricStrength); + attemptToFinish(userId, sensorId, + "lockOutFor(userId=" + userId + ", biometricStrength=" + biometricStrength + + ", sensorId=" + sensorId + ""); + } + + @Override + public void authEndedFor(int userId, @Authenticators.Types int biometricStrength, + int sensorId) { + mAuthResultCoordinator.authEndedFor(biometricStrength); + attemptToFinish(userId, sensorId, + "authEndedFor(userId=" + userId + " ,biometricStrength=" + biometricStrength + + ", sensorId=" + sensorId); + } + + @Override + public void resetLockoutFor(int userId, @Authenticators.Types int biometricStrength) { + mMultiBiometricLockoutState.onUserUnlocked(userId, biometricStrength); + } + + private void attemptToFinish(int userId, int sensorId, String description) { + boolean didFail = false; + if (!mAuthOperations.contains(sensorId)) { + Slog.e(TAG, "Error unable to find auth operation : " + description); + didFail = true; + } + if (userId != mUserId) { + Slog.e(TAG, "Error mismatched userId, expected=" + mUserId + " for " + description); + didFail = true; + } + if (didFail) { + return; + } + mAuthOperations.remove(sensorId); + if (mIsAuthenticating && mAuthOperations.isEmpty()) { + endAuthSession(); + } + } + +} diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java new file mode 100644 index 000000000000..8b1f90af0234 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java @@ -0,0 +1,49 @@ +/* + * 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.server.biometrics.sensors; + +import android.hardware.biometrics.BiometricManager.Authenticators; + +/** + * An interface that listens to authentication events. + */ +interface AuthSessionListener { + /** + * Indicates an auth operation has started for a given user and sensor. + */ + void authStartedFor(int userId, int sensorId); + + /** + * Indicates a successful authentication occurred for a sensor of a given strength. + */ + void authenticatedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId); + + /** + * Indicates authentication ended for a sensor of a given strength. + */ + void authEndedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId); + + /** + * Indicates a lockout occurred for a sensor of a given strength. + */ + void lockedOutFor(int userId, @Authenticators.Types int biometricStrength, int sensorId); + + /** + * Indicates that a reset lockout has happened for a given strength. + */ + void resetLockoutFor(int uerId, @Authenticators.Types int biometricStrength); +} diff --git a/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java new file mode 100644 index 000000000000..49dc817c7fd5 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java @@ -0,0 +1,117 @@ +/* + * 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.server.biometrics.sensors; + +import static android.hardware.biometrics.BiometricManager.Authenticators; +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE; +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG; +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK; + +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class is used as a system to store the state of each + * {@link Authenticators.Types} status for every user. + */ +class MultiBiometricLockoutState { + + private static final String TAG = "MultiBiometricLockoutState"; + private static final Map<Integer, List<Integer>> PRECEDENCE; + + static { + Map<Integer, List<Integer>> precedence = new ArrayMap<>(); + precedence.put(Authenticators.BIOMETRIC_STRONG, + Arrays.asList(BIOMETRIC_STRONG, BIOMETRIC_WEAK, BIOMETRIC_CONVENIENCE)); + precedence.put(BIOMETRIC_WEAK, Arrays.asList(BIOMETRIC_WEAK, BIOMETRIC_CONVENIENCE)); + precedence.put(BIOMETRIC_CONVENIENCE, Arrays.asList(BIOMETRIC_CONVENIENCE)); + PRECEDENCE = Collections.unmodifiableMap(precedence); + } + + private final Map<Integer, Map<Integer, Boolean>> mCanUserAuthenticate; + + @VisibleForTesting + MultiBiometricLockoutState() { + mCanUserAuthenticate = new HashMap<>(); + } + + private static Map<Integer, Boolean> createLockedOutMap() { + Map<Integer, Boolean> lockOutMap = new HashMap<>(); + lockOutMap.put(BIOMETRIC_STRONG, false); + lockOutMap.put(BIOMETRIC_WEAK, false); + lockOutMap.put(BIOMETRIC_CONVENIENCE, false); + return lockOutMap; + } + + private Map<Integer, Boolean> getAuthMapForUser(int userId) { + if (!mCanUserAuthenticate.containsKey(userId)) { + mCanUserAuthenticate.put(userId, createLockedOutMap()); + } + return mCanUserAuthenticate.get(userId); + } + + /** + * Indicates a {@link Authenticators} has been locked for userId. + * + * @param userId The user. + * @param strength The strength of biometric that is requested to be locked. + */ + void onUserLocked(int userId, @Authenticators.Types int strength) { + Slog.d(TAG, "onUserLocked(userId=" + userId + ", strength=" + strength + ")"); + Map<Integer, Boolean> canUserAuthState = getAuthMapForUser(userId); + for (int strengthToLockout : PRECEDENCE.get(strength)) { + canUserAuthState.put(strengthToLockout, false); + } + } + + /** + * Indicates that a user has unlocked a {@link Authenticators} + * + * @param userId The user. + * @param strength The strength of biometric that is unlocked. + */ + void onUserUnlocked(int userId, @Authenticators.Types int strength) { + Slog.d(TAG, "onUserUnlocked(userId=" + userId + ", strength=" + strength + ")"); + Map<Integer, Boolean> canUserAuthState = getAuthMapForUser(userId); + for (int strengthToLockout : PRECEDENCE.get(strength)) { + canUserAuthState.put(strengthToLockout, true); + } + } + + /** + * Indicates if a user can perform an authentication operation with a given + * {@link Authenticators.Types} + * + * @param userId The user. + * @param strength The strength of biometric that is requested to authenticate. + * @return If a user can authenticate with a given biometric of this strength. + */ + boolean canUserAuthenticate(int userId, @Authenticators.Types int strength) { + final boolean canAuthenticate = getAuthMapForUser(userId).get(strength); + Slog.d(TAG, "canUserAuthenticate(userId=" + userId + ", strength=" + strength + ") =" + + canAuthenticate); + return canAuthenticate; + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java new file mode 100644 index 000000000000..47b4bf547ff8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java @@ -0,0 +1,94 @@ +/* + * 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.server.biometrics.sensors; + +import static com.google.common.truth.Truth.assertThat; + +import android.hardware.biometrics.BiometricManager; + +import org.junit.Before; +import org.junit.Test; + +public class AuthResultCoordinatorTest { + private AuthResultCoordinator mAuthResultCoordinator; + + @Before + public void setUp() throws Exception { + mAuthResultCoordinator = new AuthResultCoordinator(); + } + + @Test + public void testDefaultMessage() { + checkResult(mAuthResultCoordinator.getResult(), + AuthResult.FAILED, + BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE); + } + + @Test + public void testSingleMessageCoordinator() { + mAuthResultCoordinator.authenticatedFor( + BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE); + checkResult(mAuthResultCoordinator.getResult(), + AuthResult.AUTHENTICATED, + BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE); + } + + @Test + public void testLockout() { + mAuthResultCoordinator.lockedOutFor( + BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE); + checkResult(mAuthResultCoordinator.getResult(), + AuthResult.LOCKED_OUT, + BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE); + } + + @Test + public void testHigherStrengthPrecedence() { + mAuthResultCoordinator.authenticatedFor( + BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE); + mAuthResultCoordinator.authenticatedFor( + BiometricManager.Authenticators.BIOMETRIC_WEAK); + checkResult(mAuthResultCoordinator.getResult(), + AuthResult.AUTHENTICATED, + BiometricManager.Authenticators.BIOMETRIC_WEAK); + + mAuthResultCoordinator.authenticatedFor( + BiometricManager.Authenticators.BIOMETRIC_STRONG); + checkResult(mAuthResultCoordinator.getResult(), + AuthResult.AUTHENTICATED, + BiometricManager.Authenticators.BIOMETRIC_STRONG); + } + + @Test + public void testAuthPrecedence() { + mAuthResultCoordinator.authenticatedFor( + BiometricManager.Authenticators.BIOMETRIC_WEAK); + mAuthResultCoordinator.lockedOutFor( + BiometricManager.Authenticators.BIOMETRIC_WEAK); + checkResult(mAuthResultCoordinator.getResult(), + AuthResult.AUTHENTICATED, + BiometricManager.Authenticators.BIOMETRIC_WEAK); + + } + + void checkResult(AuthResult res, int status, + @BiometricManager.Authenticators.Types int strength) { + assertThat(res.getStatus()).isEqualTo(status); + assertThat(res.getBiometricStrength()).isEqualTo(strength); + } + +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java new file mode 100644 index 000000000000..9bb0f58db520 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java @@ -0,0 +1,100 @@ +/* + * 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.server.biometrics.sensors; + +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE; +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG; +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; + +@Presubmit +@SmallTest +public class AuthSessionCoordinatorTest { + private static final int PRIMARY_USER = 0; + private static final int SECONDARY_USER = 10; + + private AuthSessionCoordinator mCoordinator; + + @Before + public void setUp() throws Exception { + mCoordinator = new AuthSessionCoordinator(); + } + + @Test + public void testUserUnlocked() { + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse(); + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse(); + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse(); + + mCoordinator.authStartedFor(PRIMARY_USER, 1); + mCoordinator.authenticatedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1); + + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse(); + } + + @Test + public void testUserCanAuthDuringLockoutOfSameSession() { + mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG); + + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue(); + + mCoordinator.authStartedFor(PRIMARY_USER, 1); + mCoordinator.authStartedFor(PRIMARY_USER, 2); + mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_WEAK, 2); + + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + } + + @Test + public void testMultiUserAuth() { + mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG); + + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue(); + + assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isFalse(); + assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_WEAK)).isFalse(); + assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_STRONG)).isFalse(); + + mCoordinator.authStartedFor(PRIMARY_USER, 1); + mCoordinator.authStartedFor(PRIMARY_USER, 2); + mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_WEAK, 2); + + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + + assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isFalse(); + assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_WEAK)).isFalse(); + assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_STRONG)).isFalse(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java new file mode 100644 index 000000000000..8baa1ce3f6c6 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java @@ -0,0 +1,148 @@ +/* + * 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.server.biometrics.sensors; + +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE; +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG; +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@Presubmit +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class MultiBiometricLockoutStateTest { + private static final int PRIMARY_USER = 0; + private MultiBiometricLockoutState mCoordinator; + + private void unlockAllBiometrics() { + unlockAllBiometrics(mCoordinator, PRIMARY_USER); + } + + private void lockoutAllBiometrics() { + lockoutAllBiometrics(mCoordinator, PRIMARY_USER); + } + + private static void unlockAllBiometrics(MultiBiometricLockoutState coordinator, int userId) { + coordinator.onUserUnlocked(userId, BIOMETRIC_STRONG); + assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_STRONG)).isTrue(); + assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_WEAK)).isTrue(); + assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_CONVENIENCE)).isTrue(); + } + + private static void lockoutAllBiometrics(MultiBiometricLockoutState coordinator, int userId) { + coordinator.onUserLocked(userId, BIOMETRIC_STRONG); + assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_STRONG)).isFalse(); + assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_WEAK)).isFalse(); + assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_CONVENIENCE)).isFalse(); + } + + @Before + public void setUp() throws Exception { + mCoordinator = new MultiBiometricLockoutState(); + } + + @Test + public void testInitialStateLockedOut() { + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse(); + } + + @Test + public void testConvenienceLockout() { + unlockAllBiometrics(); + mCoordinator.onUserLocked(PRIMARY_USER, BIOMETRIC_CONVENIENCE); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse(); + } + + @Test + public void testWeakLockout() { + unlockAllBiometrics(); + mCoordinator.onUserLocked(PRIMARY_USER, BIOMETRIC_WEAK); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse(); + } + + @Test + public void testStrongLockout() { + unlockAllBiometrics(); + mCoordinator.onUserLocked(PRIMARY_USER, BIOMETRIC_STRONG); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse(); + } + + @Test + public void testConvenienceUnlock() { + lockoutAllBiometrics(); + mCoordinator.onUserUnlocked(PRIMARY_USER, BIOMETRIC_CONVENIENCE); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); + } + + @Test + public void testWeakUnlock() { + lockoutAllBiometrics(); + mCoordinator.onUserUnlocked(PRIMARY_USER, BIOMETRIC_WEAK); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); + } + + @Test + public void testStrongUnlock() { + lockoutAllBiometrics(); + mCoordinator.onUserUnlocked(PRIMARY_USER, BIOMETRIC_STRONG); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); + } + + @Test + public void multiUser_userOneDoesNotAffectUserTwo() { + final int userOne = 1; + final int userTwo = 2; + MultiBiometricLockoutState coordinator = new MultiBiometricLockoutState(); + lockoutAllBiometrics(coordinator, userOne); + lockoutAllBiometrics(coordinator, userTwo); + + coordinator.onUserUnlocked(userOne, BIOMETRIC_WEAK); + assertThat(coordinator.canUserAuthenticate(userOne, BIOMETRIC_STRONG)).isFalse(); + assertThat(coordinator.canUserAuthenticate(userOne, BIOMETRIC_WEAK)).isTrue(); + assertThat(coordinator.canUserAuthenticate(userOne, BIOMETRIC_CONVENIENCE)).isTrue(); + + assertThat(coordinator.canUserAuthenticate(userTwo, BIOMETRIC_STRONG)).isFalse(); + assertThat(coordinator.canUserAuthenticate(userTwo, BIOMETRIC_WEAK)).isFalse(); + assertThat(coordinator.canUserAuthenticate(userTwo, BIOMETRIC_CONVENIENCE)).isFalse(); + } +} |