diff options
21 files changed, 697 insertions, 244 deletions
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 3f139f02efaf..2eb8cb9f2f81 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -212,6 +212,26 @@ public class BiometricManager { * @see android.security.keystore.KeyGenParameterSpec.Builder */ int DEVICE_CREDENTIAL = 1 << 15; + + } + + /** + * @hide + * returns a string representation of an authenticator type. + */ + @NonNull public static String authenticatorToStr(@Authenticators.Types int authenticatorType) { + switch(authenticatorType) { + case Authenticators.BIOMETRIC_STRONG: + return "BIOMETRIC_STRONG"; + case Authenticators.BIOMETRIC_WEAK: + return "BIOMETRIC_WEAK"; + case Authenticators.BIOMETRIC_CONVENIENCE: + return "BIOMETRIC_CONVENIENCE"; + case Authenticators.DEVICE_CREDENTIAL: + return "DEVICE_CREDENTIAL"; + default: + return "Unknown authenticator type: " + authenticatorType; + } } /** diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java index 8265203ddacd..be04364dcff3 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java @@ -21,11 +21,15 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.common.OperationContext; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; + import java.util.function.Consumer; /** * Cache for system state not directly related to biometric operations that is used for * logging or optimizations. + * + * This class is also used to inject dependencies such as {@link AuthSessionCoordinator} */ public interface BiometricContext { /** Gets the context source from the system context. */ @@ -59,4 +63,7 @@ public interface BiometricContext { /** Unsubscribe from context changes. */ void unsubscribe(@NonNull OperationContext context); + + /** Obtains an AuthSessionCoordinator. */ + AuthSessionCoordinator getAuthSessionCoordinator(); } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java index 3d1a6343cbf2..23b2714dd50b 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java @@ -35,6 +35,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.statusbar.ISessionListener; import com.android.internal.statusbar.IStatusBarService; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -59,7 +60,8 @@ final class BiometricContextProvider implements BiometricContext { sInstance = new BiometricContextProvider( new AmbientDisplayConfiguration(context), IStatusBarService.Stub.asInterface(ServiceManager.getServiceOrThrow( - Context.STATUS_BAR_SERVICE)), null /* handler */); + Context.STATUS_BAR_SERVICE)), null /* handler */, + new AuthSessionCoordinator()); } catch (ServiceNotFoundException e) { throw new IllegalStateException("Failed to find required service", e); } @@ -76,13 +78,16 @@ final class BiometricContextProvider implements BiometricContext { private final Map<Integer, InstanceId> mSession = new ConcurrentHashMap<>(); private final AmbientDisplayConfiguration mAmbientDisplayConfiguration; + private final AuthSessionCoordinator mAuthSessionCoordinator; private boolean mIsAod = false; private boolean mIsAwake = false; @VisibleForTesting BiometricContextProvider(@NonNull AmbientDisplayConfiguration ambientDisplayConfiguration, - @NonNull IStatusBarService service, @Nullable Handler handler) { + @NonNull IStatusBarService service, @Nullable Handler handler, + AuthSessionCoordinator authSessionCoordinator) { mAmbientDisplayConfiguration = ambientDisplayConfiguration; + mAuthSessionCoordinator = authSessionCoordinator; try { service.setBiometicContextListener(new IBiometricContextListener.Stub() { @Override @@ -190,6 +195,11 @@ final class BiometricContextProvider implements BiometricContext { mSubscribers.remove(context); } + @Override + public AuthSessionCoordinator getAuthSessionCoordinator() { + return mAuthSessionCoordinator; + } + private void notifySubscribers() { mSubscribers.forEach((context, consumer) -> { context.isAod = isAod(); diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java index 6d00c3fc15ea..bdae5f3eac32 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java @@ -17,9 +17,11 @@ package com.android.server.biometrics.sensors; import android.hardware.biometrics.BiometricManager.Authenticators; +import android.util.ArrayMap; -import java.util.ArrayList; -import java.util.List; +import java.util.Collections; +import java.util.Map; +import java.util.function.IntFunction; /** * A class that takes in a series of authentication attempts (successes, failures, lockouts) @@ -30,64 +32,64 @@ import java.util.List; */ class AuthResultCoordinator { + /** + * Indicates no change has occurred with this authenticator. + */ + static final int AUTHENTICATOR_DEFAULT = 0; + /** + * Indicated this authenticator has received a lockout. + */ + static final int AUTHENTICATOR_LOCKED = 1 << 0; + /** + * Indicates this authenticator has received a successful unlock. + */ + static final int AUTHENTICATOR_UNLOCKED = 1 << 1; private static final String TAG = "AuthResultCoordinator"; - private final List<AuthResult> mOperations; + private final Map<Integer, Integer> mAuthenticatorState; AuthResultCoordinator() { - mOperations = new ArrayList<>(); + mAuthenticatorState = new ArrayMap<>(); + mAuthenticatorState.put(Authenticators.BIOMETRIC_STRONG, AUTHENTICATOR_DEFAULT); + mAuthenticatorState.put(Authenticators.BIOMETRIC_WEAK, AUTHENTICATOR_DEFAULT); + mAuthenticatorState.put(Authenticators.BIOMETRIC_CONVENIENCE, AUTHENTICATOR_DEFAULT); } - /** - * 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)); + private void updateState(@Authenticators.Types int strength, IntFunction<Integer> mapper) { + switch (strength) { + case Authenticators.BIOMETRIC_STRONG: + mAuthenticatorState.put(Authenticators.BIOMETRIC_STRONG, + mapper.apply(mAuthenticatorState.get(Authenticators.BIOMETRIC_STRONG))); + // fall through + case Authenticators.BIOMETRIC_WEAK: + mAuthenticatorState.put(Authenticators.BIOMETRIC_WEAK, + mapper.apply(mAuthenticatorState.get(Authenticators.BIOMETRIC_WEAK))); + // fall through + case Authenticators.BIOMETRIC_CONVENIENCE: + mAuthenticatorState.put(Authenticators.BIOMETRIC_CONVENIENCE, + mapper.apply( + mAuthenticatorState.get(Authenticators.BIOMETRIC_CONVENIENCE))); + } } /** - * Adds auth ended for a given strength to the current operation list. + * Adds auth success for a given strength to the current operation list. */ - void authEndedFor(@Authenticators.Types int strength) { - mOperations.add(new AuthResult(AuthResult.FAILED, strength)); + void authenticatedFor(@Authenticators.Types int strength) { + updateState(strength, (old) -> AUTHENTICATOR_UNLOCKED | old); } /** * 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)); + updateState(strength, (old) -> AUTHENTICATOR_LOCKED | old); } /** - * Obtains an auth result & strength from a current set of biometric operations. + * Returns the current authenticator state. Each authenticator will have + * the associated operations that were performed on them(DEFAULT, LOCKED, UNLOCKED). */ - 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(); + final Map<Integer, Integer> getResult() { + return Collections.unmodifiableMap(mAuthenticatorState); } } - - diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java index 13840ff389d9..dec1b559556a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java @@ -16,10 +16,22 @@ package com.android.server.biometrics.sensors; +import static com.android.server.biometrics.sensors.AuthResultCoordinator.AUTHENTICATOR_LOCKED; +import static com.android.server.biometrics.sensors.AuthResultCoordinator.AUTHENTICATOR_UNLOCKED; + import android.hardware.biometrics.BiometricManager.Authenticators; +import android.os.SystemClock; +import android.util.Pair; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -28,20 +40,31 @@ import java.util.Set; * 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 { +public class AuthSessionCoordinator implements AuthSessionListener { private static final String TAG = "AuthSessionCoordinator"; private final Set<Integer> mAuthOperations; + private final MultiBiometricLockoutState mMultiBiometricLockoutState; + private final List<Pair<Integer, Long>> mTimedLockouts; + private final RingBuffer mRingBuffer; + private final Clock mClock; private int mUserId; private boolean mIsAuthenticating; private AuthResultCoordinator mAuthResultCoordinator; - private MultiBiometricLockoutState mMultiBiometricLockoutState; - AuthSessionCoordinator() { + public AuthSessionCoordinator() { + this(SystemClock.currentNetworkTimeClock()); + } + + @VisibleForTesting + AuthSessionCoordinator(Clock clock) { mAuthOperations = new HashSet<>(); mAuthResultCoordinator = new AuthResultCoordinator(); - mMultiBiometricLockoutState = new MultiBiometricLockoutState(); + mMultiBiometricLockoutState = new MultiBiometricLockoutState(clock); + mRingBuffer = new RingBuffer(100); + mTimedLockouts = new ArrayList<>(); + mClock = clock; } /** @@ -51,7 +74,9 @@ class AuthSessionCoordinator implements AuthSessionListener { mAuthOperations.clear(); mUserId = userId; mIsAuthenticating = true; - mAuthResultCoordinator.resetState(); + mAuthOperations.clear(); + mAuthResultCoordinator = new AuthResultCoordinator(); + mRingBuffer.addApiCall("internal : onAuthSessionStarted(" + userId + ")"); } /** @@ -64,14 +89,27 @@ class AuthSessionCoordinator implements AuthSessionListener { 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()); + final long currentTime = mClock.millis(); + for (Pair<Integer, Long> timedLockouts : mTimedLockouts) { + mMultiBiometricLockoutState.increaseLockoutTime(mUserId, timedLockouts.first, + timedLockouts.second + currentTime); + } + // User unlocks can also unlock timed lockout Authenticator.Types + final Map<Integer, Integer> result = mAuthResultCoordinator.getResult(); + for (int authenticator : Arrays.asList(Authenticators.BIOMETRIC_CONVENIENCE, + Authenticators.BIOMETRIC_WEAK, Authenticators.BIOMETRIC_STRONG)) { + final Integer value = result.get(authenticator); + if ((value & AUTHENTICATOR_UNLOCKED) == AUTHENTICATOR_UNLOCKED) { + mMultiBiometricLockoutState.setAuthenticatorTo(mUserId, authenticator, + true /* canAuthenticate */); + mMultiBiometricLockoutState.clearLockoutTime(mUserId, authenticator); + } else if ((value & AUTHENTICATOR_LOCKED) == AUTHENTICATOR_LOCKED) { + mMultiBiometricLockoutState.setAuthenticatorTo(mUserId, authenticator, + false /* canAuthenticate */); + } + } - mAuthResultCoordinator.resetState(); + mRingBuffer.addApiCall("internal : onAuthSessionEnded(" + mUserId + ")"); mIsAuthenticating = false; } } @@ -79,12 +117,15 @@ class AuthSessionCoordinator implements AuthSessionListener { /** * @return true if a user can authenticate with a given strength. */ - boolean getCanAuthFor(int userId, @Authenticators.Types int strength) { + public boolean getCanAuthFor(int userId, @Authenticators.Types int strength) { return mMultiBiometricLockoutState.canUserAuthenticate(userId, strength); } @Override - public void authStartedFor(int userId, int sensorId) { + public void authStartedFor(int userId, int sensorId, long requestId) { + mRingBuffer.addApiCall( + "authStartedFor(userId=" + userId + ", sensorId=" + sensorId + ", requestId=" + + requestId + ")"); if (!mIsAuthenticating) { onAuthSessionStarted(userId); } @@ -105,34 +146,58 @@ class AuthSessionCoordinator implements AuthSessionListener { @Override public void authenticatedFor(int userId, @Authenticators.Types int biometricStrength, - int sensorId) { + int sensorId, long requestId) { + final String authStr = + "authenticatedFor(userId=" + userId + ", strength=" + biometricStrength + + " , sensorId=" + sensorId + ", requestId= " + requestId + ")"; + mRingBuffer.addApiCall(authStr); mAuthResultCoordinator.authenticatedFor(biometricStrength); - attemptToFinish(userId, sensorId, - "authenticatedFor(userId=" + userId + ", biometricStrength=" + biometricStrength - + ", sensorId=" + sensorId + ""); + attemptToFinish(userId, sensorId, authStr); } @Override public void lockedOutFor(int userId, @Authenticators.Types int biometricStrength, - int sensorId) { - mAuthResultCoordinator.lockedOutFor(biometricStrength); - attemptToFinish(userId, sensorId, + int sensorId, long requestId) { + final String lockedOutStr = "lockOutFor(userId=" + userId + ", biometricStrength=" + biometricStrength - + ", sensorId=" + sensorId + ""); + + ", sensorId=" + sensorId + ", requestId=" + requestId + ")"; + mRingBuffer.addApiCall(lockedOutStr); + mAuthResultCoordinator.lockedOutFor(biometricStrength); + attemptToFinish(userId, sensorId, lockedOutStr); + } + + @Override + public void lockOutTimed(int userId, @Authenticators.Types int biometricStrength, int sensorId, + long time, long requestId) { + final String lockedOutStr = + "lockOutTimedFor(userId=" + userId + ", biometricStrength=" + biometricStrength + + ", sensorId=" + sensorId + "time=" + time + ", requestId=" + requestId + + ")"; + mRingBuffer.addApiCall(lockedOutStr); + mTimedLockouts.add(new Pair<>(biometricStrength, time)); + attemptToFinish(userId, sensorId, lockedOutStr); } @Override public void authEndedFor(int userId, @Authenticators.Types int biometricStrength, - int sensorId) { - mAuthResultCoordinator.authEndedFor(biometricStrength); - attemptToFinish(userId, sensorId, + int sensorId, long requestId) { + final String authEndedStr = "authEndedFor(userId=" + userId + " ,biometricStrength=" + biometricStrength - + ", sensorId=" + sensorId); + + ", sensorId=" + sensorId + ", requestId=" + requestId + ")"; + mRingBuffer.addApiCall(authEndedStr); + attemptToFinish(userId, sensorId, authEndedStr); } @Override - public void resetLockoutFor(int userId, @Authenticators.Types int biometricStrength) { - mMultiBiometricLockoutState.onUserUnlocked(userId, biometricStrength); + public void resetLockoutFor(int userId, @Authenticators.Types int biometricStrength, + long requestId) { + final String resetLockStr = + "resetLockoutFor(userId=" + userId + " ,biometricStrength=" + biometricStrength + + ", requestId=" + requestId + ")"; + mRingBuffer.addApiCall(resetLockStr); + mMultiBiometricLockoutState.setAuthenticatorTo(userId, biometricStrength, + true /*canAuthenticate */); + mMultiBiometricLockoutState.clearLockoutTime(userId, biometricStrength); } private void attemptToFinish(int userId, int sensorId, String description) { @@ -154,4 +219,49 @@ class AuthSessionCoordinator implements AuthSessionListener { } } + /** + * Returns a string representation of the past N API calls as well as the + * permanent and timed lockout states for each user's authenticators. + */ + @Override + public String toString() { + return mRingBuffer + "\n" + mMultiBiometricLockoutState; + } + + private static class RingBuffer { + private final String[] mApiCalls; + private final int mSize; + private int mCurr; + private int mApiCallNumber; + + RingBuffer(int size) { + if (size <= 0) { + Slog.wtf(TAG, "Cannot initialize ring buffer of size: " + size); + } + mApiCalls = new String[size]; + mCurr = 0; + mSize = size; + mApiCallNumber = 0; + } + + void addApiCall(String str) { + mApiCalls[mCurr] = str; + mCurr++; + mCurr %= mSize; + mApiCallNumber++; + } + + @Override + public String toString() { + String buffer = ""; + int apiCall = mApiCallNumber > mSize ? mApiCallNumber - mSize : 0; + for (int i = 0; i < mSize; i++) { + final int location = (mCurr + i) % mSize; + if (mApiCalls[location] != null) { + buffer += String.format("#%-5d %s\n", apiCall++, mApiCalls[location]); + } + } + return buffer; + } + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java index 8b1f90af0234..d97f793a84bc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java @@ -25,25 +25,34 @@ interface AuthSessionListener { /** * Indicates an auth operation has started for a given user and sensor. */ - void authStartedFor(int userId, int sensorId); + void authStartedFor(int userId, int sensorId, long requestId); /** * Indicates a successful authentication occurred for a sensor of a given strength. */ - void authenticatedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId); + void authenticatedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId, + long requestId); /** * Indicates authentication ended for a sensor of a given strength. */ - void authEndedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId); + void authEndedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId, + long requestId); /** * Indicates a lockout occurred for a sensor of a given strength. */ - void lockedOutFor(int userId, @Authenticators.Types int biometricStrength, int sensorId); + void lockedOutFor(int userId, @Authenticators.Types int biometricStrength, int sensorId, + long requestId); + + /** + * Indicates a timed lockout occurred for a sensor of a given strength. + */ + void lockOutTimed(int userId, @Authenticators.Types int biometricStrength, int sensorId, + long duration, long requestId); /** * Indicates that a reset lockout has happened for a given strength. */ - void resetLockoutFor(int uerId, @Authenticators.Types int biometricStrength); + void resetLockoutFor(int uerId, @Authenticators.Types int biometricStrength, long requestId); } diff --git a/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java index 49dc817c7fd5..d9bd04d3f1c8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java +++ b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java @@ -21,82 +21,93 @@ import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMET import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG; import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK; -import android.util.ArrayMap; +import android.hardware.biometrics.BiometricManager; +import android.os.SystemClock; import android.util.Slog; -import com.android.internal.annotations.VisibleForTesting; - -import java.util.Arrays; -import java.util.Collections; +import java.time.Clock; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * This class is used as a system to store the state of each * {@link Authenticators.Types} status for every user. + * + * Note that initially all biomertics are unlocked, meaning users can authenticate + * with each strength. */ 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; + private final Map<Integer, Map<Integer, AuthenticatorState>> mCanUserAuthenticate; + private final Clock mClock; - @VisibleForTesting MultiBiometricLockoutState() { + this(SystemClock.currentNetworkTimeClock()); + } + + MultiBiometricLockoutState(Clock clock) { mCanUserAuthenticate = new HashMap<>(); + mClock = clock; } - 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); + private Map<Integer, AuthenticatorState> createUnlockedMap() { + Map<Integer, AuthenticatorState> lockOutMap = new HashMap<>(); + lockOutMap.put(BIOMETRIC_STRONG, + new AuthenticatorState(BIOMETRIC_STRONG, false, 0, mClock)); + lockOutMap.put(BIOMETRIC_WEAK, new AuthenticatorState(BIOMETRIC_WEAK, false, 0, mClock)); + lockOutMap.put(BIOMETRIC_CONVENIENCE, + new AuthenticatorState(BIOMETRIC_CONVENIENCE, false, 0, mClock)); return lockOutMap; } - private Map<Integer, Boolean> getAuthMapForUser(int userId) { + private Map<Integer, AuthenticatorState> getAuthMapForUser(int userId) { if (!mCanUserAuthenticate.containsKey(userId)) { - mCanUserAuthenticate.put(userId, createLockedOutMap()); + mCanUserAuthenticate.put(userId, createUnlockedMap()); } 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); + void setAuthenticatorTo(int userId, @Authenticators.Types int strength, boolean canAuth) { + final Map<Integer, AuthenticatorState> authMap = getAuthMapForUser(userId); + switch (strength) { + case Authenticators.BIOMETRIC_STRONG: + authMap.get(BIOMETRIC_STRONG).mPermanentlyLockedOut = !canAuth; + // fall through + case Authenticators.BIOMETRIC_WEAK: + authMap.get(BIOMETRIC_WEAK).mPermanentlyLockedOut = !canAuth; + // fall through + case Authenticators.BIOMETRIC_CONVENIENCE: + authMap.get(BIOMETRIC_CONVENIENCE).mPermanentlyLockedOut = !canAuth; } } - /** - * 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); + void increaseLockoutTime(int userId, @Authenticators.Types int strength, long duration) { + final Map<Integer, AuthenticatorState> authMap = getAuthMapForUser(userId); + switch (strength) { + case Authenticators.BIOMETRIC_STRONG: + authMap.get(BIOMETRIC_STRONG).increaseLockoutTo(duration); + // fall through + case Authenticators.BIOMETRIC_WEAK: + authMap.get(BIOMETRIC_WEAK).increaseLockoutTo(duration); + // fall through + case Authenticators.BIOMETRIC_CONVENIENCE: + authMap.get(BIOMETRIC_CONVENIENCE).increaseLockoutTo(duration); + } + } + + void clearLockoutTime(int userId, @Authenticators.Types int strength) { + final Map<Integer, AuthenticatorState> authMap = getAuthMapForUser(userId); + switch (strength) { + case Authenticators.BIOMETRIC_STRONG: + authMap.get(BIOMETRIC_STRONG).setTimedLockout(0); + // fall through + case Authenticators.BIOMETRIC_WEAK: + authMap.get(BIOMETRIC_WEAK).setTimedLockout(0); + // fall through + case Authenticators.BIOMETRIC_CONVENIENCE: + authMap.get(BIOMETRIC_CONVENIENCE).setTimedLockout(0); } } @@ -109,9 +120,64 @@ class MultiBiometricLockoutState { * @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); + final boolean canAuthenticate = getAuthMapForUser(userId).get(strength).canAuthenticate(); Slog.d(TAG, "canUserAuthenticate(userId=" + userId + ", strength=" + strength + ") =" + canAuthenticate); return canAuthenticate; } + + @Override + public String toString() { + String dumpState = "Permanent Lockouts\n"; + final long time = mClock.millis(); + for (Map.Entry<Integer, Map<Integer, AuthenticatorState>> userState : + mCanUserAuthenticate.entrySet()) { + final int userId = userState.getKey(); + final Map<Integer, AuthenticatorState> map = userState.getValue(); + String prettyStr = map.entrySet().stream().map( + (Map.Entry<Integer, AuthenticatorState> entry) -> entry.getValue().toString( + time)).collect(Collectors.joining(", ")); + dumpState += "UserId=" + userId + ", {" + prettyStr + "}\n"; + } + return dumpState; + } + + private static class AuthenticatorState { + private Integer mAuthenticatorType; + private boolean mPermanentlyLockedOut; + private long mTimedLockout; + private Clock mClock; + + AuthenticatorState(Integer authenticatorId, boolean permanentlyLockedOut, + long timedLockout, Clock clock) { + mAuthenticatorType = authenticatorId; + mPermanentlyLockedOut = permanentlyLockedOut; + mTimedLockout = timedLockout; + mClock = clock; + } + + boolean canAuthenticate() { + return !mPermanentlyLockedOut && mClock.millis() - mTimedLockout >= 0; + } + + /** + * Either increases the lockout to duration, or leaves it as it, whichever is longer. + */ + void increaseLockoutTo(long duration) { + mTimedLockout = Math.max(mTimedLockout, duration); + } + + void setTimedLockout(long duration) { + mTimedLockout = duration; + } + + String toString(long currentTime) { + final String duration = + mTimedLockout - currentTime > 0 ? (mTimedLockout - currentTime) + "ms" : "none"; + final String permanentLockout = mPermanentlyLockedOut ? "true" : "false"; + return String.format("(%s, permanentLockout=%s, timedLockoutRemaining=%s)", + BiometricManager.authenticatorToStr(mAuthenticatorType), permanentLockout, + duration); + } + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 2e4c32340320..c27d71f2f079 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -25,6 +25,7 @@ import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.face.IFace; import android.hardware.face.FaceAuthenticationFrame; @@ -38,6 +39,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -58,18 +60,25 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> implements LockoutConsumer { private static final String TAG = "FaceAuthenticationClient"; - @NonNull private final UsageStats mUsageStats; - @NonNull private final LockoutCache mLockoutCache; - @Nullable private final NotificationManager mNotificationManager; - @Nullable private ICancellationSignal mCancellationSignal; - @Nullable private SensorPrivacyManager mSensorPrivacyManager; - + @NonNull + private final UsageStats mUsageStats; + @NonNull + private final LockoutCache mLockoutCache; + @NonNull + private final AuthSessionCoordinator mAuthSessionCoordinator; + @Nullable + private final NotificationManager mNotificationManager; private final int[] mBiometricPromptIgnoreList; private final int[] mBiometricPromptIgnoreListVendor; private final int[] mKeyguardIgnoreList; private final int[] mKeyguardIgnoreListVendor; - - @FaceManager.FaceAcquired private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN; + private final int mBiometricStrength; + @Nullable + private ICancellationSignal mCancellationSignal; + @Nullable + private SensorPrivacyManager mSensorPrivacyManager; + @FaceManager.FaceAcquired + private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN; FaceAuthenticationClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @@ -79,11 +88,12 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull UsageStats usageStats, @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, - boolean isKeyguardBypassEnabled) { + boolean isKeyguardBypassEnabled, @Authenticators.Types int biometricStrength) { this(context, lazyDaemon, token, requestId, listener, targetUserId, operationId, restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext, isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication, - isKeyguardBypassEnabled, context.getSystemService(SensorPrivacyManager.class)); + isKeyguardBypassEnabled, context.getSystemService(SensorPrivacyManager.class), + biometricStrength); } @VisibleForTesting @@ -95,7 +105,8 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull UsageStats usageStats, @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, - boolean isKeyguardBypassEnabled, SensorPrivacyManager sensorPrivacyManager) { + boolean isKeyguardBypassEnabled, SensorPrivacyManager sensorPrivacyManager, + @Authenticators.Types int biometricStrength) { super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext, isStrongBiometric, null /* taskStackListener */, lockoutCache, @@ -107,6 +118,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> mLockoutCache = lockoutCache; mNotificationManager = context.getSystemService(NotificationManager.class); mSensorPrivacyManager = sensorPrivacyManager; + mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator(); final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -117,12 +129,14 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> R.array.config_face_acquire_keyguard_ignorelist); mKeyguardIgnoreListVendor = resources.getIntArray( R.array.config_face_acquire_vendor_keyguard_ignorelist); + mBiometricStrength = biometricStrength; } @Override public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); mState = STATE_STARTED; + mAuthSessionCoordinator.authStartedFor(getTargetUserId(), getSensorId(), getRequestId()); } @NonNull @@ -138,7 +152,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> if (mSensorPrivacyManager != null && mSensorPrivacyManager .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, - SensorPrivacyManager.Sensors.CAMERA)) { + SensorPrivacyManager.Sensors.CAMERA)) { onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); mCallback.onClientFinished(this, false /* success */); @@ -207,6 +221,9 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> 0 /* error */, 0 /* vendorError */, getTargetUserId())); + mAuthSessionCoordinator + .authenticatedFor(getTargetUserId(), mBiometricStrength, getSensorId(), + getRequestId()); } @Override @@ -222,7 +239,8 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> if (error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL) { BiometricNotificationUtils.showReEnrollmentNotification(getContext()); } - + mAuthSessionCoordinator.authEndedFor(getTargetUserId(), mBiometricStrength, getSensorId(), + getRequestId()); super.onError(error, vendorCode); } @@ -283,6 +301,8 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } + mAuthSessionCoordinator.lockOutTimed(getTargetUserId(), mBiometricStrength, getSensorId(), + durationMillis, getRequestId()); } @Override @@ -298,5 +318,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } + mAuthSessionCoordinator.lockedOutFor(getTargetUserId(), mBiometricStrength, getSensorId(), + getRequestId()); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index 6bff179e8eb7..b60f9d80d425 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -414,6 +414,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) { mHandler.post(() -> { final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); + final int biometricStrength = Utils.getCurrentStrength(sensorId); final FaceAuthenticationClient client = new FaceAuthenticationClient( mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback, userId, operationId, restricted, opPackageName, cookie, @@ -421,7 +422,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), mBiometricContext, isStrongBiometric, mUsageStats, mSensors.get(sensorId).getLockoutCache(), - allowBackgroundAuthentication, isKeyguardBypassEnabled); + allowBackgroundAuthentication, isKeyguardBypassEnabled, biometricStrength); scheduleForSensor(sensorId, client); }); } @@ -490,7 +491,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN), mBiometricContext, hardwareAuthToken, - mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher); + mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher, + Utils.getCurrentStrength(sensorId)); scheduleForSensor(sensorId, client); }); @@ -623,6 +625,9 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } pw.println(dump); pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount()); + pw.println("---AuthSessionCoordinator logs begin---"); + pw.println(mBiometricContext.getAuthSessionCoordinator()); + pw.println("---AuthSessionCoordinator logs end ---"); mSensors.get(sensorId).getScheduler().dump(pw); mUsageStats.print(pw); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index bf7a62aadc28..32bed48aec95 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -18,6 +18,7 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.content.Context; +import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.face.IFace; import android.hardware.keymaster.HardwareAuthToken; import android.os.RemoteException; @@ -48,17 +49,20 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem private final HardwareAuthToken mHardwareAuthToken; private final LockoutCache mLockoutCache; private final LockoutResetDispatcher mLockoutResetDispatcher; + private final int mBiometricStrength; FaceResetLockoutClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker, - @NonNull LockoutResetDispatcher lockoutResetDispatcher) { + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @Authenticators.Types int biometricStrength) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, 0 /* cookie */, sensorId, logger, biometricContext); mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken); mLockoutCache = lockoutTracker; mLockoutResetDispatcher = lockoutResetDispatcher; + mBiometricStrength = biometricStrength; } @Override @@ -85,6 +89,8 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem void onLockoutCleared() { resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache, mLockoutResetDispatcher); + getBiometricContext().getAuthSessionCoordinator() + .resetLockoutFor(getTargetUserId(), mBiometricStrength, getRequestId()); mCallback.onClientFinished(this, true /* success */); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index e0955b7dd450..ba583eb42dbc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired; +import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.fingerprint.PointerContext; @@ -45,6 +46,7 @@ import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; import com.android.server.biometrics.log.Probe; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -81,7 +83,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> private final Handler mHandler; private final int mSkipWaitForPowerAcquireMessage; private final int mSkipWaitForPowerVendorAcquireMessage; + private final int mBiometricStrength; private final long mFingerUpIgnoresPower = 500; + private final AuthSessionCoordinator mAuthSessionCoordinator; @Nullable private ICancellationSignal mCancellationSignal; private boolean mIsPointerDown; @@ -112,7 +116,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> @Nullable ISidefpsController sidefpsController, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps, - @NonNull Handler handler) { + @NonNull Handler handler, + @Authenticators.Types int biometricStrength) { super( context, lazyDaemon, @@ -154,6 +159,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mSkipWaitForPowerVendorAcquireMessage = context.getResources().getInteger( R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage); + mBiometricStrength = biometricStrength; + mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator(); if (mSensorProps.isAnySidefpsType()) { if (Build.isDebuggable()) { @@ -180,6 +187,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> } else { mState = STATE_STARTED; } + mAuthSessionCoordinator.authStartedFor(getTargetUserId(), getSensorId(), + getRequestId()); } @NonNull @@ -193,6 +202,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> protected void handleLifecycleAfterAuth(boolean authenticated) { if (authenticated) { mCallback.onClientFinished(this, true /* success */); + mAuthSessionCoordinator.authenticatedFor( + getTargetUserId(), mBiometricStrength, getSensorId(), getRequestId()); } } @@ -294,6 +305,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> } mSensorOverlays.hide(getSensorId()); + mAuthSessionCoordinator.authEndedFor(getTargetUserId(), mBiometricStrength, getSensorId(), + getRequestId()); } @Override @@ -447,6 +460,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); + mAuthSessionCoordinator.lockOutTimed(getTargetUserId(), mBiometricStrength, getSensorId(), + durationMillis, getRequestId()); } @Override @@ -470,6 +485,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); + mAuthSessionCoordinator.lockedOutFor(getTargetUserId(), mBiometricStrength, getSensorId(), + getRequestId()); } @Override @@ -482,6 +499,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mHandler.removeMessages(MESSAGE_AUTH_SUCCESS); // Do not call onError() as that will send an additional callback to coex. onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true); + mAuthSessionCoordinator.authEndedFor(getTargetUserId(), + mBiometricStrength, getSensorId(), getRequestId()); } mHandler.removeMessages(MESSAGE_IGNORE_AUTH); mHandler.postDelayed(() -> { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 3fe6332fcaa0..774aff1dd72c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -328,7 +328,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN), mBiometricContext, hardwareAuthToken, - mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher); + mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher, + Utils.getCurrentStrength(sensorId)); scheduleForSensor(sensorId, client); }); } @@ -447,7 +448,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mBiometricContext, isStrongBiometric, mTaskStackListener, mSensors.get(sensorId).getLockoutCache(), mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, - mSensors.get(sensorId).getSensorProperties(), mHandler); + mSensors.get(sensorId).getSensorProperties(), mHandler, + Utils.getCurrentStrength(sensorId)); scheduleForSensor(sensorId, client, mBiometricStateCallback); }); } @@ -702,6 +704,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } pw.println(dump); pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount()); + pw.println("---AuthSessionCoordinator logs begin---"); + pw.println(mBiometricContext.getAuthSessionCoordinator()); + pw.println("---AuthSessionCoordinator logs end ---"); mSensors.get(sensorId).getScheduler().dump(pw); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index c8148df9ea71..22f504cf5664 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -18,6 +18,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.annotation.NonNull; import android.content.Context; +import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.keymaster.HardwareAuthToken; import android.os.RemoteException; @@ -48,17 +49,20 @@ class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implem private final HardwareAuthToken mHardwareAuthToken; private final LockoutCache mLockoutCache; private final LockoutResetDispatcher mLockoutResetDispatcher; + private final int mBiometricStrength; FingerprintResetLockoutClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId, @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker, - @NonNull LockoutResetDispatcher lockoutResetDispatcher) { + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @Authenticators.Types int biometricStrength) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, 0 /* cookie */, sensorId, biometricLogger, biometricContext); mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken); mLockoutCache = lockoutTracker; mLockoutResetDispatcher = lockoutResetDispatcher; + mBiometricStrength = biometricStrength; } @Override @@ -89,6 +93,8 @@ class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implem void onLockoutCleared() { resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache, mLockoutResetDispatcher); + getBiometricContext().getAuthSessionCoordinator() + .resetLockoutFor(getTargetUserId(), mBiometricStrength, getRequestId()); mCallback.onClientFinished(this, true /* success */); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java index dd7aeb72f7d2..58f338c7a5bb 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java @@ -74,7 +74,7 @@ public class BiometricContextProviderTest { public void setup() throws RemoteException { when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); mProvider = new BiometricContextProvider(mAmbientDisplayConfiguration, mStatusBarService, - null /* handler */); + null /* handler */, null /* authSessionCoordinator */); ArgumentCaptor<IBiometricContextListener> captor = ArgumentCaptor.forClass(IBiometricContextListener.class); verify(mStatusBarService).setBiometicContextListener(captor.capture()); 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 index 47b4bf547ff8..c5a855732e0a 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java @@ -16,6 +16,10 @@ package com.android.server.biometrics.sensors; +import static com.android.server.biometrics.sensors.AuthResultCoordinator.AUTHENTICATOR_DEFAULT; +import static com.android.server.biometrics.sensors.AuthResultCoordinator.AUTHENTICATOR_LOCKED; +import static com.android.server.biometrics.sensors.AuthResultCoordinator.AUTHENTICATOR_UNLOCKED; + import static com.google.common.truth.Truth.assertThat; import android.hardware.biometrics.BiometricManager; @@ -23,6 +27,8 @@ import android.hardware.biometrics.BiometricManager; import org.junit.Before; import org.junit.Test; +import java.util.Map; + public class AuthResultCoordinatorTest { private AuthResultCoordinator mAuthResultCoordinator; @@ -33,62 +39,107 @@ public class AuthResultCoordinatorTest { @Test public void testDefaultMessage() { - checkResult(mAuthResultCoordinator.getResult(), - AuthResult.FAILED, - BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE); + final Map<Integer, Integer> authMap = mAuthResultCoordinator.getResult(); + + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo( + AUTHENTICATOR_DEFAULT); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo( + AUTHENTICATOR_DEFAULT); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo( + AUTHENTICATOR_DEFAULT); } @Test public void testSingleMessageCoordinator() { mAuthResultCoordinator.authenticatedFor( BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE); - checkResult(mAuthResultCoordinator.getResult(), - AuthResult.AUTHENTICATED, - BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE); + + final Map<Integer, Integer> authMap = mAuthResultCoordinator.getResult(); + + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo( + AUTHENTICATOR_DEFAULT); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo( + AUTHENTICATOR_DEFAULT); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo( + AUTHENTICATOR_UNLOCKED); } @Test public void testLockout() { mAuthResultCoordinator.lockedOutFor( BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE); - checkResult(mAuthResultCoordinator.getResult(), - AuthResult.LOCKED_OUT, - BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE); + + final Map<Integer, Integer> authMap = mAuthResultCoordinator.getResult(); + + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo( + AUTHENTICATOR_DEFAULT); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo( + AUTHENTICATOR_DEFAULT); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo( + AUTHENTICATOR_LOCKED); } @Test - public void testHigherStrengthPrecedence() { + public void testConvenientLockout() { mAuthResultCoordinator.authenticatedFor( BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE); + + Map<Integer, Integer> authMap = mAuthResultCoordinator.getResult(); + + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo( + AUTHENTICATOR_DEFAULT); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo( + AUTHENTICATOR_DEFAULT); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo( + AUTHENTICATOR_UNLOCKED); + } + + @Test + public void testWeakLockout() { mAuthResultCoordinator.authenticatedFor( BiometricManager.Authenticators.BIOMETRIC_WEAK); - checkResult(mAuthResultCoordinator.getResult(), - AuthResult.AUTHENTICATED, - BiometricManager.Authenticators.BIOMETRIC_WEAK); + Map<Integer, Integer> authMap = mAuthResultCoordinator.getResult(); + + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo( + AUTHENTICATOR_DEFAULT); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo( + AUTHENTICATOR_UNLOCKED); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo( + AUTHENTICATOR_UNLOCKED); + } + + @Test + public void testStrongLockout() { mAuthResultCoordinator.authenticatedFor( BiometricManager.Authenticators.BIOMETRIC_STRONG); - checkResult(mAuthResultCoordinator.getResult(), - AuthResult.AUTHENTICATED, - BiometricManager.Authenticators.BIOMETRIC_STRONG); + + final Map<Integer, Integer> authMap = mAuthResultCoordinator.getResult(); + + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo( + AUTHENTICATOR_UNLOCKED); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo( + AUTHENTICATOR_UNLOCKED); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo( + AUTHENTICATOR_UNLOCKED); } @Test - public void testAuthPrecedence() { + public void testAuthAndLockout() { mAuthResultCoordinator.authenticatedFor( BiometricManager.Authenticators.BIOMETRIC_WEAK); mAuthResultCoordinator.lockedOutFor( BiometricManager.Authenticators.BIOMETRIC_WEAK); - checkResult(mAuthResultCoordinator.getResult(), - AuthResult.AUTHENTICATED, - BiometricManager.Authenticators.BIOMETRIC_WEAK); - } + final Map<Integer, Integer> authMap = mAuthResultCoordinator.getResult(); + + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo( + AUTHENTICATOR_DEFAULT); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo( + AUTHENTICATOR_UNLOCKED | AUTHENTICATOR_LOCKED); + assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo( + AUTHENTICATOR_UNLOCKED | AUTHENTICATOR_LOCKED); - 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 index 9bb0f58db520..6e4487548d64 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java @@ -22,12 +22,18 @@ import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMET import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.time.Clock; @Presubmit @SmallTest @@ -36,20 +42,51 @@ public class AuthSessionCoordinatorTest { private static final int SECONDARY_USER = 10; private AuthSessionCoordinator mCoordinator; + @Mock + private Clock mClock; @Before public void setUp() throws Exception { - mCoordinator = new AuthSessionCoordinator(); + MockitoAnnotations.initMocks(this); + when(mClock.millis()).thenReturn(0L); + mCoordinator = new AuthSessionCoordinator(mClock); } @Test public void testUserUnlocked() { + mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */); + mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_STRONG, 1 /* sensorId */, + 0 /* requestId */); + 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); + mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */); + mCoordinator.authenticatedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1 /* sensorId */, + 0 /* requestId */); + + 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 testUserLocked() { + mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */); + mCoordinator.authStartedFor(PRIMARY_USER, 2 /* sensorId */, 0 /* requestId */); + mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_STRONG, 1 /* sensorId */, + 0 /* requestId */); + mCoordinator.authenticatedFor(PRIMARY_USER, BIOMETRIC_WEAK, 2 /* sensorId */, + 0 /* requestId */); + + 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 /* sensorId */, 0 /* requestId */); + mCoordinator.authenticatedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1 /* sensorId */, + 0 /* requestId */); assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); @@ -58,15 +95,16 @@ public class AuthSessionCoordinatorTest { @Test public void testUserCanAuthDuringLockoutOfSameSession() { - mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG); + mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG, 0 /* requestId */); 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); + mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */); + mCoordinator.authStartedFor(PRIMARY_USER, 2 /* sensorId */, 0 /* requestId */); + mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_WEAK, 2 /* sensorId */, + 0 /* requestId */); assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); @@ -75,7 +113,15 @@ public class AuthSessionCoordinatorTest { @Test public void testMultiUserAuth() { - mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG); + mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */); + mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_STRONG, 1 /* sensorId */, + 0 /* requestId */); + + mCoordinator.authStartedFor(SECONDARY_USER, 1 /* sensorId */, 0 /* requestId */); + mCoordinator.lockedOutFor(SECONDARY_USER, BIOMETRIC_STRONG, 1 /* sensorId */, + 0 /* requestId */); + + mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG, 0 /* requestId */); assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); @@ -85,9 +131,10 @@ public class AuthSessionCoordinatorTest { 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); + mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */); + mCoordinator.authStartedFor(PRIMARY_USER, 2 /* sensorId */, 0 /* requestId */); + mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_WEAK, 2 /* sensorId */, + 0 /* requestId */); assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); 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 index 8baa1ce3f6c6..0b10a7b35127 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java @@ -22,6 +22,8 @@ import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMET import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + import android.platform.test.annotations.Presubmit; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -31,6 +33,10 @@ import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.time.Clock; @SmallTest @Presubmit @@ -38,111 +44,155 @@ import org.junit.runner.RunWith; @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 MultiBiometricLockoutState mLockoutState; + @Mock + private Clock mClock; + + private static void unlockAllBiometrics(MultiBiometricLockoutState lockoutState, int userId) { + lockoutState.setAuthenticatorTo(userId, BIOMETRIC_STRONG, true /* canAuthenticate */); + assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_STRONG)).isTrue(); + assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_WEAK)).isTrue(); + assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_CONVENIENCE)).isTrue(); } - private void lockoutAllBiometrics() { - lockoutAllBiometrics(mCoordinator, PRIMARY_USER); + private static void lockoutAllBiometrics(MultiBiometricLockoutState lockoutState, int userId) { + lockoutState.setAuthenticatorTo(userId, BIOMETRIC_STRONG, false /* canAuthenticate */); + assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_STRONG)).isFalse(); + assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_WEAK)).isFalse(); + assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_CONVENIENCE)).isFalse(); } - 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 void unlockAllBiometrics() { + unlockAllBiometrics(mLockoutState, PRIMARY_USER); } - 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(); + private void lockoutAllBiometrics() { + lockoutAllBiometrics(mLockoutState, PRIMARY_USER); } @Before public void setUp() throws Exception { - mCoordinator = new MultiBiometricLockoutState(); + MockitoAnnotations.initMocks(this); + when(mClock.millis()).thenReturn(0L); + mLockoutState = new MultiBiometricLockoutState(mClock); } @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(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); } @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(); + mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_CONVENIENCE, + false /* canAuthenticate */); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat( + mLockoutState.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(); + mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_WEAK, false /* canAuthenticate */); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse(); + assertThat( + mLockoutState.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(); + lockoutAllBiometrics(); + mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_STRONG, + false /* canAuthenticate */); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse(); + assertThat( + mLockoutState.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(); + mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_CONVENIENCE, + true /* canAuthenticate */); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse(); + assertThat(mLockoutState.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(); + mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_WEAK, true /* canAuthenticate */); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mLockoutState.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(); + mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_STRONG, + true /* canAuthenticate */); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mLockoutState.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(); + MultiBiometricLockoutState lockoutState = new MultiBiometricLockoutState(mClock); + lockoutAllBiometrics(lockoutState, userOne); + lockoutAllBiometrics(lockoutState, userTwo); + + lockoutState.setAuthenticatorTo(userOne, BIOMETRIC_WEAK, true /* canAuthenticate */); + assertThat(lockoutState.canUserAuthenticate(userOne, BIOMETRIC_STRONG)).isFalse(); + assertThat(lockoutState.canUserAuthenticate(userOne, BIOMETRIC_WEAK)).isTrue(); + assertThat(lockoutState.canUserAuthenticate(userOne, BIOMETRIC_CONVENIENCE)).isTrue(); + + assertThat(lockoutState.canUserAuthenticate(userTwo, BIOMETRIC_STRONG)).isFalse(); + assertThat(lockoutState.canUserAuthenticate(userTwo, BIOMETRIC_WEAK)).isFalse(); + assertThat(lockoutState.canUserAuthenticate(userTwo, BIOMETRIC_CONVENIENCE)).isFalse(); + } + + @Test + public void testTimedLockout() { + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); + + mLockoutState.increaseLockoutTime(PRIMARY_USER, BIOMETRIC_STRONG, + System.currentTimeMillis() + 1); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse(); + assertThat( + mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse(); + } + + @Test + public void testTimedLockoutAfterDuration() { + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); + + when(mClock.millis()).thenReturn(0L); + mLockoutState.increaseLockoutTime(PRIMARY_USER, BIOMETRIC_STRONG, 1); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse(); + assertThat( + mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse(); + + when(mClock.millis()).thenReturn(2L); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue(); + assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue(); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java index f08d0ef67df3..2dc3583478fb 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java @@ -44,6 +44,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutCache; @@ -95,6 +96,8 @@ public class FaceAuthenticationClientTest { private ActivityTaskManager mActivityTaskManager; @Mock private ICancellationSignal mCancellationSignal; + @Mock + private AuthSessionCoordinator mAuthSessionCoordinator; @Captor private ArgumentCaptor<OperationContext> mOperationContextCaptor; @@ -105,6 +108,7 @@ public class FaceAuthenticationClientTest { public void setup() { when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer( i -> i.getArgument(0)); + when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); } @Test @@ -158,7 +162,8 @@ public class FaceAuthenticationClientTest { false /* requireConfirmation */, 9 /* sensorId */, mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, mUsageStats, mLockoutCache, false /* allowBackgroundAuthentication */, - false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */) { + false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */, + 0 /* biometricStrength */) { @Override protected ActivityTaskManager getActivityTaskManager() { return mActivityTaskManager; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index 518946aa761a..2afc4d7f8e40 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -71,6 +72,8 @@ public class SensorTest { private BiometricLogger mBiometricLogger; @Mock private BiometricContext mBiometricContext; + @Mock + private AuthSessionCoordinator mAuthSessionCoordinator; private final TestLooper mLooper = new TestLooper(); private final LockoutCache mLockoutCache = new LockoutCache(); @@ -84,6 +87,8 @@ public class SensorTest { when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService); + when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); + mScheduler = new UserAwareBiometricScheduler(TAG, new Handler(mLooper.getLooper()), BiometricScheduler.SENSOR_TYPE_FACE, @@ -107,7 +112,7 @@ public class SensorTest { mScheduler.scheduleClientMonitor(new FaceResetLockoutClient(mContext, () -> new AidlSession(1, mSession, USER_ID, mHalCallback), USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, - HAT, mLockoutCache, mLockoutResetDispatcher)); + HAT, mLockoutCache, mLockoutResetDispatcher, 0 /* biometricStrength */)); mLooper.dispatchAll(); verifyNotLocked(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index dea4d4fb7c64..d5893df991fd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -58,6 +58,7 @@ import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; import com.android.server.biometrics.log.Probe; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutCache; @@ -127,6 +128,8 @@ public class FingerprintAuthenticationClientTest { private ICancellationSignal mCancellationSignal; @Mock private Probe mLuxProbe; + @Mock + private AuthSessionCoordinator mAuthSessionCoordinator; @Captor private ArgumentCaptor<OperationContext> mOperationContextCaptor; @Captor @@ -138,6 +141,7 @@ public class FingerprintAuthenticationClientTest { @Before public void setup() { mContext.addMockSystemService(BiometricManager.class, mBiometricManager); + when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); when(mBiometricLogger.getAmbientLightProbe(anyBoolean())).thenAnswer(i -> new CallbackWithProbe<>(mLuxProbe, i.getArgument(0))); when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer( @@ -496,7 +500,7 @@ public class FingerprintAuthenticationClientTest { null /* taskStackListener */, mLockoutCache, mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps, - new Handler(mLooper.getLooper())) { + new Handler(mLooper.getLooper()), 0 /* biometricStrength */) { @Override protected ActivityTaskManager getActivityTaskManager() { return mActivityTaskManager; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index ff636c840bad..7ae4e17e394d 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -71,6 +72,8 @@ public class SensorTest { private BiometricLogger mLogger; @Mock private BiometricContext mBiometricContext; + @Mock + private AuthSessionCoordinator mAuthSessionCoordinator; private final TestLooper mLooper = new TestLooper(); private final LockoutCache mLockoutCache = new LockoutCache(); @@ -83,6 +86,7 @@ public class SensorTest { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService); + when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); mScheduler = new UserAwareBiometricScheduler(TAG, new Handler(mLooper.getLooper()), @@ -107,7 +111,7 @@ public class SensorTest { mScheduler.scheduleClientMonitor(new FingerprintResetLockoutClient(mContext, () -> new AidlSession(1, mSession, USER_ID, mHalCallback), USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT, mLockoutCache, - mLockoutResetDispatcher)); + mLockoutResetDispatcher, 0 /* biometricStrength */)); mLooper.dispatchAll(); verifyNotLocked(); |