Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8a3582ea authored by Joshua Mccloskey's avatar Joshua Mccloskey Committed by Android (Google) Code Review
Browse files

Merge "Integrate Session Coordinator"

parents 0eee4af3 5945a4b8
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -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;
        }
    }

    /**
+7 −0
Original line number Diff line number Diff line
@@ -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();
}
+12 −2
Original line number Diff line number Diff line
@@ -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();
+44 −42
Original line number Diff line number Diff line
@@ -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;
    final Map<Integer, Integer> getResult() {
        return Collections.unmodifiableMap(mAuthenticatorState);
    }
                    } else {
                        // curr is a weaker biometric
                        return next;
}
                }));
    }

    void resetState() {
        mOperations.clear();
    }
}

+138 −28
Original line number Diff line number Diff line
@@ -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());
            }
            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;
        }
    }
@@ -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;
        }
    }
}
Loading