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

Commit b60d6333 authored by Kevin Chyn's avatar Kevin Chyn
Browse files

4/n: Start moving fingerprint lockout to its own class

Bug: 157790417

Test: Fingerprint lockout on keyguard, wait 30s, lockout is reset
Change-Id: Icdb8c6ffe70b6334f8981e8bce5bea8c98907c68
parent 8e3d7577
Loading
Loading
Loading
Loading
+0 −4
Original line number Diff line number Diff line
@@ -4462,10 +4462,6 @@
    <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
        android:protectionLevel="signature" />

    <!-- Allows an app to reset face authentication attempt counter. Reserved for the system. @hide -->
    <permission android:name="android.permission.RESET_FACE_LOCKOUT"
        android:protectionLevel="signature" />

    <!-- Allows an application to control keyguard.  Only allowed for system processes.
        @hide -->
    <permission android:name="android.permission.CONTROL_KEYGUARD"
+4 −4
Original line number Diff line number Diff line
@@ -37,8 +37,8 @@ import java.util.ArrayList;
 */
public abstract class AuthenticationClient extends ClientMonitor {

    public abstract int handleFailedAttempt();
    public void resetFailedAttempts() {}
    public abstract int handleFailedAttempt(int userId);
    public void resetFailedAttempts(int userId) {}

    public static final int LOCKOUT_NONE = 0;
    public static final int LOCKOUT_TIMED = 1;
@@ -179,7 +179,7 @@ public abstract class AuthenticationClient extends ClientMonitor {
                }
                result = true;
                if (mShouldFrameworkHandleLockout) {
                    resetFailedAttempts();
                    resetFailedAttempts(getTargetUserId());
                }
                onStop();

@@ -222,7 +222,7 @@ public abstract class AuthenticationClient extends ClientMonitor {
                }

                // Allow system-defined limit of number of attempts before giving up
                final int lockoutMode = handleFailedAttempt();
                final int lockoutMode = handleFailedAttempt(getTargetUserId());
                if (lockoutMode != LOCKOUT_NONE && mShouldFrameworkHandleLockout) {
                    Slog.w(getLogTag(), "Forcing lockout (driver code should do this!), mode("
                            + lockoutMode + ")");
+7 −18
Original line number Diff line number Diff line
@@ -169,17 +169,6 @@ public abstract class BiometricServiceBase extends SystemService
     */
    protected abstract void updateActiveGroup(int userId, String clientPackage);

    /**
     * @return The protected intent to reset lockout for a specific biometric.
     */
    protected abstract String getLockoutResetIntent();

    /**
     * @return The permission the sender is required to have in order for the lockout reset intent
     *         to be received by the BiometricService implementation.
     */
    protected abstract String getLockoutBroadcastPermission();

    /**
     * @param userId
     * @return Returns true if the user has any enrolled biometrics.
@@ -218,7 +207,7 @@ public abstract class BiometricServiceBase extends SystemService
    /**
     * @return one of the AuthenticationClient LOCKOUT constants
     */
    protected abstract int getLockoutMode();
    protected abstract int getLockoutMode(int userId);

    protected abstract class AuthenticationClientImpl extends AuthenticationClient {

@@ -235,8 +224,8 @@ public abstract class BiometricServiceBase extends SystemService
        }

        @Override
        public int handleFailedAttempt() {
            final int lockoutMode = getLockoutMode();
        public int handleFailedAttempt(int userId) {
            final int lockoutMode = getLockoutMode(userId);
            if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
                mPerformanceStats.permanentLockout++;
            } else if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) {
@@ -407,8 +396,7 @@ public abstract class BiometricServiceBase extends SystemService
                context.getResources().getString(R.string.config_keyguardComponent));
        mKeyguardPackage = keyguardComponent != null ? keyguardComponent.getPackageName() : null;
        mAppOps = context.getSystemService(AppOpsManager.class);
        mActivityTaskManager = ((ActivityTaskManager) context.getSystemService(
                Context.ACTIVITY_TASK_SERVICE)).getService();
        mActivityTaskManager = ActivityTaskManager.getService();
        mPowerManager = mContext.getSystemService(PowerManager.class);
        mUserManager = UserManager.get(mContext);
        mMetricsLogger = new MetricsLogger();
@@ -476,7 +464,8 @@ public abstract class BiometricServiceBase extends SystemService
        if (client != null && client.onAcquired(acquiredInfo, vendorCode)) {
            removeClient(client);
        }
        if (mPerformanceStats != null && getLockoutMode() == AuthenticationClient.LOCKOUT_NONE
        if (mPerformanceStats != null
                && getLockoutMode(client.getTargetUserId()) == AuthenticationClient.LOCKOUT_NONE
                && client instanceof AuthenticationClient) {
            // ignore enrollment acquisitions or acquisitions when we're locked out
            mPerformanceStats.acquire++;
@@ -692,7 +681,7 @@ public abstract class BiometricServiceBase extends SystemService
    private void startAuthentication(AuthenticationClientImpl client, String opPackageName) {
        if (DEBUG) Slog.v(getTag(), "startAuthentication(" + opPackageName + ")");

        int lockoutMode = getLockoutMode();
        int lockoutMode = getLockoutMode(client.getTargetUserId());
        if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
            Slog.v(getTag(), "In lockout mode(" + lockoutMode + ") ; disallowing authentication");
            int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?
+1 −12
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.server.biometrics.sensors.face;

import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.RESET_FACE_LOCKOUT;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;

import android.app.ActivityManager;
@@ -1046,16 +1045,6 @@ public class FaceService extends BiometricServiceBase {
        }
    }

    @Override
    protected String getLockoutResetIntent() {
        return ACTION_LOCKOUT_RESET;
    }

    @Override
    protected String getLockoutBroadcastPermission() {
        return RESET_FACE_LOCKOUT;
    }

    @Override
    protected void handleUserSwitching(int userId) {
        super.handleUserSwitching(userId);
@@ -1103,7 +1092,7 @@ public class FaceService extends BiometricServiceBase {
    }

    @Override
    protected int getLockoutMode() {
    protected int getLockoutMode(int userId) {
        return mCurrentUserLockoutMode;
    }

+21 −118
Original line number Diff line number Diff line
@@ -25,13 +25,8 @@ import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;

import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricConstants;
@@ -51,12 +46,9 @@ import android.os.IBinder;
import android.os.NativeHandle;
import android.os.RemoteException;
import android.os.SELinux;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.Surface;

@@ -64,16 +56,15 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
import com.android.server.biometrics.sensors.BiometricServiceBase;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.Constants;
import com.android.server.biometrics.sensors.EnrollClient;
import com.android.server.biometrics.sensors.RemovalClient;
import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;

import org.json.JSONArray;
import org.json.JSONException;
@@ -99,31 +90,6 @@ public class FingerprintService extends BiometricServiceBase {
    protected static final String TAG = "FingerprintService";
    private static final boolean DEBUG = true;
    private static final String FP_DATA_DIR = "fpdata";
    private static final String ACTION_LOCKOUT_RESET =
            "com.android.server.biometrics.fingerprint.ACTION_LOCKOUT_RESET";
    private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5;
    private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 20;
    private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30 * 1000;
    private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user";

    private final class ResetFailedAttemptsForUserRunnable implements Runnable {
        @Override
        public void run() {
            resetFailedAttemptsForUser(true /* clearAttemptCounter */,
                    ActivityManager.getCurrentUser());
        }
    }

    private final class LockoutReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Slog.v(getTag(), "Resetting lockout: " + intent.getAction());
            if (getLockoutResetIntent().equals(intent.getAction())) {
                final int user = intent.getIntExtra(KEY_LOCKOUT_RESET_USER, 0);
                resetFailedAttemptsForUser(false /* clearAttemptCounter */, user);
            }
        }
    }

    private final class FingerprintAuthClient extends AuthenticationClientImpl {
        public FingerprintAuthClient(Context context,
@@ -139,9 +105,8 @@ public class FingerprintService extends BiometricServiceBase {
        }

        @Override
        public void resetFailedAttempts() {
            resetFailedAttemptsForUser(true /* clearAttemptCounter */,
                    ActivityManager.getCurrentUser());
        public void resetFailedAttempts(int userId) {
            mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
        }

        @Override
@@ -151,16 +116,9 @@ public class FingerprintService extends BiometricServiceBase {
        }

        @Override
        public int handleFailedAttempt() {
            final int currentUser = ActivityManager.getCurrentUser();
            mFailedAttempts.put(currentUser, mFailedAttempts.get(currentUser, 0) + 1);
            mTimedLockoutCleared.put(ActivityManager.getCurrentUser(), false);

            if (getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
                scheduleLockoutResetForUser(currentUser);
            }

            return super.handleFailedAttempt();
        public int handleFailedAttempt(int userId) {
            mLockoutTracker.addFailedAttemptForUser(userId);
            return super.handleFailedAttempt(userId);
        }
    }

@@ -390,7 +348,10 @@ public class FingerprintService extends BiometricServiceBase {
            }

            // TODO: confirm security token when we move timeout management into the HAL layer.
            mHandler.post(mResetFailedAttemptsForCurrentUserRunnable);
            mHandler.post(() -> {
                mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */,
                        mCurrentUserId);
            });
        }

        @Override
@@ -420,18 +381,17 @@ public class FingerprintService extends BiometricServiceBase {
        }
    }

    private final LockoutTracker mLockoutTracker;
    private final FingerprintConstants mFingerprintConstants = new FingerprintConstants();
    private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks =
            new CopyOnWriteArrayList<>();

    @GuardedBy("this")
    private IBiometricsFingerprint mDaemon;
    private final SparseBooleanArray mTimedLockoutCleared;
    private final SparseIntArray mFailedAttempts;
    private final AlarmManager mAlarmManager;
    private final LockoutReceiver mLockoutReceiver = new LockoutReceiver();
    protected final ResetFailedAttemptsForUserRunnable mResetFailedAttemptsForCurrentUserRunnable =
            new ResetFailedAttemptsForUserRunnable();

    private final LockoutTracker.LockoutResetCallback mLockoutResetCallback = userId -> {
        notifyLockoutResetMonitors();
    };

    /**
     * Receives callbacks from the HAL.
@@ -573,11 +533,7 @@ public class FingerprintService extends BiometricServiceBase {

    public FingerprintService(Context context) {
        super(context);
        mTimedLockoutCleared = new SparseBooleanArray();
        mFailedAttempts = new SparseIntArray();
        mAlarmManager = context.getSystemService(AlarmManager.class);
        context.registerReceiver(mLockoutReceiver, new IntentFilter(getLockoutResetIntent()),
                getLockoutBroadcastPermission(), null /* handler */);
        mLockoutTracker = new LockoutTracker(context, mLockoutResetCallback);
    }

    @Override
@@ -671,16 +627,6 @@ public class FingerprintService extends BiometricServiceBase {
        }
    }

    @Override
    protected String getLockoutResetIntent() {
        return ACTION_LOCKOUT_RESET;
    }

    @Override
    protected String getLockoutBroadcastPermission() {
        return RESET_FINGERPRINT_LOCKOUT;
    }

    @Override
    protected boolean hasEnrolledBiometrics(int userId) {
        if (userId != UserHandle.getCallingUserId()) {
@@ -742,17 +688,8 @@ public class FingerprintService extends BiometricServiceBase {
    }

    @Override
    protected int getLockoutMode() {
        final int currentUser = ActivityManager.getCurrentUser();
        final int failedAttempts = mFailedAttempts.get(currentUser, 0);
        if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
            return AuthenticationClient.LOCKOUT_PERMANENT;
        } else if (failedAttempts > 0
                && !mTimedLockoutCleared.get(currentUser, false)
                && (failedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
            return AuthenticationClient.LOCKOUT_TIMED;
        }
        return AuthenticationClient.LOCKOUT_NONE;
    protected int getLockoutMode(int userId) {
        return mLockoutTracker.getLockoutModeForUser(userId);
    }

    /** Gets the fingerprint daemon */
@@ -823,40 +760,6 @@ public class FingerprintService extends BiometricServiceBase {
        return 0;
    }

    // Attempt counter should only be cleared when Keyguard goes away or when
    // a biometric is successfully authenticated. Lockout should eventually be done below the HAL.
    // See AuthenticationClient#shouldFrameworkHandleLockout().
    private void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
        if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
            Slog.v(getTag(), "Reset biometric lockout, clearAttemptCounter=" + clearAttemptCounter);
        }
        if (clearAttemptCounter) {
            mFailedAttempts.put(userId, 0);
        }
        mTimedLockoutCleared.put(userId, true);
        // If we're asked to reset failed attempts externally (i.e. from Keyguard),
        // the alarm might still be pending; remove it.
        cancelLockoutResetForUser(userId);
        notifyLockoutResetMonitors();
    }


    private void cancelLockoutResetForUser(int userId) {
        mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
    }

    private void scheduleLockoutResetForUser(int userId) {
        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
                getLockoutResetIntentForUser(userId));
    }

    private PendingIntent getLockoutResetIntentForUser(int userId) {
        return PendingIntent.getBroadcast(getContext(), userId,
                new Intent(getLockoutResetIntent()).putExtra(KEY_LOCKOUT_RESET_USER, userId),
                PendingIntent.FLAG_UPDATE_CURRENT);
    }

    private native NativeHandle convertSurfaceToNativeHandle(Surface surface);

    private void dumpInternal(PrintWriter pw) {
Loading