Loading core/java/android/hardware/biometrics/BiometricManager.java +20 −0 Original line number Diff line number Diff line Loading @@ -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; } } /** Loading services/core/java/com/android/server/biometrics/log/BiometricContext.java +7 −0 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -59,4 +63,7 @@ public interface BiometricContext { /** Unsubscribe from context changes. */ void unsubscribe(@NonNull OperationContext context); /** Obtains an AuthSessionCoordinator. */ AuthSessionCoordinator getAuthSessionCoordinator(); } services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java +12 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } Loading @@ -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 Loading Loading @@ -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(); Loading services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java +44 −42 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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; final Map<Integer, Integer> getResult() { return Collections.unmodifiableMap(mAuthenticatorState); } } else { // curr is a weaker biometric return next; } })); } void resetState() { mOperations.clear(); } } services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java +138 −28 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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; } /** Loading @@ -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 + ")"); } /** Loading @@ -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()); } mAuthResultCoordinator.resetState(); 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 */); } } mRingBuffer.addApiCall("internal : onAuthSessionEnded(" + mUserId + ")"); mIsAuthenticating = false; } } Loading @@ -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); } Loading @@ -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) { Loading @@ -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; } } } Loading
core/java/android/hardware/biometrics/BiometricManager.java +20 −0 Original line number Diff line number Diff line Loading @@ -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; } } /** Loading
services/core/java/com/android/server/biometrics/log/BiometricContext.java +7 −0 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -59,4 +63,7 @@ public interface BiometricContext { /** Unsubscribe from context changes. */ void unsubscribe(@NonNull OperationContext context); /** Obtains an AuthSessionCoordinator. */ AuthSessionCoordinator getAuthSessionCoordinator(); }
services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java +12 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } Loading @@ -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 Loading Loading @@ -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(); Loading
services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java +44 −42 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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; final Map<Integer, Integer> getResult() { return Collections.unmodifiableMap(mAuthenticatorState); } } else { // curr is a weaker biometric return next; } })); } void resetState() { mOperations.clear(); } }
services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java +138 −28 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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; } /** Loading @@ -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 + ")"); } /** Loading @@ -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()); } mAuthResultCoordinator.resetState(); 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 */); } } mRingBuffer.addApiCall("internal : onAuthSessionEnded(" + mUserId + ")"); mIsAuthenticating = false; } } Loading @@ -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); } Loading @@ -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) { Loading @@ -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; } } }