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

Commit 1147fedd authored by Kevin Chyn's avatar Kevin Chyn
Browse files

5/n: Virtualize biometric performance stats

Moves the performance tracker to its own singleton class. This should
allow us to decouple it from wherever it's accessed (e.g. ClientMonitor)

Bug: 157790417
Test: 1) Keyguard/BiometricPromptDemo auth, acquire, reject with
         multiple users, with and without crypto, lockout,
         permanentLockout
      2) Kill HAL few times
      3a) adb shell dumpsys fingrprint
      3b) adb shell dumpsys face

Change-Id: I49bcf161d29be35ba9339855a0c62931f71c2f87
parent b60d6333
Loading
Loading
Loading
Loading
+24 −44
Original line number Diff line number Diff line
@@ -116,25 +116,9 @@ public abstract class BiometricServiceBase extends SystemService
    private IBiometricService mBiometricService;
    private ClientMonitor mCurrentClient;
    private ClientMonitor mPendingClient;
    private PerformanceStats mPerformanceStats;
    private PerformanceTracker mPerformanceTracker;
    private int mSensorId;
    protected int mCurrentUserId = UserHandle.USER_NULL;
    // Tracks if the current authentication makes use of CryptoObjects.
    protected boolean mIsCrypto;
    // Normal authentications are tracked by mPerformanceMap.
    protected HashMap<Integer, PerformanceStats> mPerformanceMap = new HashMap<>();
    // Transactions that make use of CryptoObjects are tracked by mCryptoPerformaceMap.
    protected HashMap<Integer, PerformanceStats> mCryptoPerformanceMap = new HashMap<>();
    protected int mHALDeathCount;

    protected class PerformanceStats {
        public int accept; // number of accepted biometrics
        public int reject; // number of rejected biometrics
        public int acquire; // total number of acquisitions. Should be >= accept+reject due to poor
        // image acquisition in some cases (too fast, too slow, dirty sensor, etc.)
        public int lockout; // total number of lockouts
        public int permanentLockout; // total number of permanent lockouts
    }

    /**
     * @return the log tag.
@@ -226,10 +210,13 @@ public abstract class BiometricServiceBase extends SystemService
        @Override
        public int handleFailedAttempt(int userId) {
            final int lockoutMode = getLockoutMode(userId);
            PerformanceTracker performanceTracker = PerformanceTracker.getInstanceForSensorId(
                    BiometricServiceBase.this.getSensorId());

            if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
                mPerformanceStats.permanentLockout++;
                performanceTracker.incrementPermanentLockoutForUser(userId);
            } else if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) {
                mPerformanceStats.lockout++;
                performanceTracker.incrementTimedLockoutForUser(userId);
            }

            // Failing multiple times will continue to push out the lockout time
@@ -399,6 +386,7 @@ public abstract class BiometricServiceBase extends SystemService
        mActivityTaskManager = ActivityTaskManager.getService();
        mPowerManager = mContext.getSystemService(PowerManager.class);
        mUserManager = UserManager.get(mContext);
        mPerformanceTracker = PerformanceTracker.getInstanceForSensorId(getSensorId());
        mMetricsLogger = new MetricsLogger();
    }

@@ -411,7 +399,7 @@ public abstract class BiometricServiceBase extends SystemService
    public void serviceDied(long cookie) {
        Slog.e(getTag(), "HAL died");
        mMetricsLogger.count(getConstants().tagHalDied(), 1);
        mHALDeathCount++;
        mPerformanceTracker.incrementHALDeathCount();
        mCurrentUserId = UserHandle.USER_NULL;

        // All client lifecycle must be managed on the handler.
@@ -464,11 +452,12 @@ public abstract class BiometricServiceBase extends SystemService
        if (client != null && client.onAcquired(acquiredInfo, vendorCode)) {
            removeClient(client);
        }
        if (mPerformanceStats != null
                && getLockoutMode(client.getTargetUserId()) == AuthenticationClient.LOCKOUT_NONE
                && client instanceof AuthenticationClient) {
            // ignore enrollment acquisitions or acquisitions when we're locked out
            mPerformanceStats.acquire++;

        if (client instanceof AuthenticationClient) {
            final int userId = client.getTargetUserId();
            if (getLockoutMode(userId) == AuthenticationClient.LOCKOUT_NONE) {
                mPerformanceTracker.incrementAcquireForUser(userId, client.isCryptoOperation());
            }
        }
    }

@@ -477,13 +466,16 @@ public abstract class BiometricServiceBase extends SystemService
        ClientMonitor client = mCurrentClient;
        final boolean authenticated = identifier.getBiometricId() != 0;

        if (client != null && client.onAuthenticated(identifier, authenticated, token)) {
        if (client != null) {
            final int userId = client.getTargetUserId();
            if (client.isCryptoOperation()) {
                mPerformanceTracker.incrementCryptoAuthForUser(userId, authenticated);
            } else {
                mPerformanceTracker.incrementAuthForUser(userId, authenticated);
            }
            if (client.onAuthenticated(identifier, authenticated, token)) {
                removeClient(client);
            }
        if (authenticated) {
            mPerformanceStats.accept++;
        } else {
            mPerformanceStats.reject++;
        }
    }

@@ -596,18 +588,6 @@ public abstract class BiometricServiceBase extends SystemService

        mHandler.post(() -> {
            mMetricsLogger.histogram(getConstants().tagAuthToken(), opId != 0L ? 1 : 0);

            // Get performance stats object for this user.
            HashMap<Integer, PerformanceStats> pmap
                    = (opId == 0) ? mPerformanceMap : mCryptoPerformanceMap;
            PerformanceStats stats = pmap.get(mCurrentUserId);
            if (stats == null) {
                stats = new PerformanceStats();
                pmap.put(mCurrentUserId, stats);
            }
            mPerformanceStats = stats;
            mIsCrypto = (opId != 0);

            startAuthentication(client, opPackageName);
        });
    }
+154 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.biometrics.sensors;

import android.util.SparseArray;

/**
 * Tracks biometric performance across sensors and users.
 */
public class PerformanceTracker {

    private static final String TAG = "PerformanceTracker";
    // Keyed by SensorId
    private static SparseArray<PerformanceTracker> sTrackers;

    public static PerformanceTracker getInstanceForSensorId(int sensorId) {
        if (sTrackers == null) {
            sTrackers = new SparseArray<>();
        }

        if (!sTrackers.contains(sensorId)) {
            sTrackers.put(sensorId, new PerformanceTracker());
        }
        return sTrackers.get(sensorId);
    }

    private static class Info {
        int mAccept; // number of accepted biometrics
        int mReject; // number of rejected biometrics
        int mAcquire; // total number of acquisitions.

        int mAcceptCrypto;
        int mRejectCrypto;
        int mAcquireCrypto;

        int mTimedLockout; // total number of lockouts
        int mPermanentLockout; // total number of permanent lockouts
    }

    // Keyed by UserId
    private final SparseArray<Info> mAllUsersInfo;
    private int mHALDeathCount;

    private PerformanceTracker() {
        mAllUsersInfo = new SparseArray<>();
    }

    private void createUserEntryIfNecessary(int userId) {
        if (!mAllUsersInfo.contains(userId)) {
            mAllUsersInfo.put(userId, new Info());
        }
    }

    void incrementAuthForUser(int userId, boolean accepted) {
        createUserEntryIfNecessary(userId);

        if (accepted) {
            mAllUsersInfo.get(userId).mAccept++;
        } else {
            mAllUsersInfo.get(userId).mReject++;
        }
    }

    void incrementCryptoAuthForUser(int userId, boolean accepted) {
        createUserEntryIfNecessary(userId);

        if (accepted) {
            mAllUsersInfo.get(userId).mAcceptCrypto++;
        } else {
            mAllUsersInfo.get(userId).mRejectCrypto++;
        }
    }

    void incrementAcquireForUser(int userId, boolean isCrypto) {
        createUserEntryIfNecessary(userId);

        if (isCrypto) {
            mAllUsersInfo.get(userId).mAcquireCrypto++;
        } else {
            mAllUsersInfo.get(userId).mAcquire++;
        }
    }

    void incrementTimedLockoutForUser(int userId) {
        createUserEntryIfNecessary(userId);

        mAllUsersInfo.get(userId).mTimedLockout++;
    }

    void incrementPermanentLockoutForUser(int userId) {
        createUserEntryIfNecessary(userId);

        mAllUsersInfo.get(userId).mPermanentLockout++;
    }

    void incrementHALDeathCount() {
        mHALDeathCount++;
    }

    public void clear() {
        mAllUsersInfo.clear();
        mHALDeathCount = 0;
    }

    public int getAcceptForUser(int userId) {
        return mAllUsersInfo.contains(userId) ? mAllUsersInfo.get(userId).mAccept : 0;
    }

    public int getRejectForUser(int userId) {
        return mAllUsersInfo.contains(userId) ? mAllUsersInfo.get(userId).mReject : 0;
    }

    public int getAcquireForUser(int userId) {
        return mAllUsersInfo.contains(userId) ? mAllUsersInfo.get(userId).mAcquire : 0;
    }

    public int getAcceptCryptoForUser(int userId) {
        return mAllUsersInfo.contains(userId) ? mAllUsersInfo.get(userId).mAcceptCrypto : 0;
    }

    public int getRejectCryptoForUser(int userId) {
        return mAllUsersInfo.contains(userId) ? mAllUsersInfo.get(userId).mRejectCrypto : 0;
    }

    public int getAcquireCryptoForUser(int userId) {
        return mAllUsersInfo.contains(userId) ? mAllUsersInfo.get(userId).mAcquireCrypto : 0;
    }

    public int getTimedLockoutForUser(int userId) {
        return mAllUsersInfo.contains(userId) ? mAllUsersInfo.get(userId).mTimedLockout : 0;
    }

    public int getPermanentLockoutForUser(int userId) {
        return mAllUsersInfo.contains(userId) ? mAllUsersInfo.get(userId).mPermanentLockout : 0;
    }

    public int getHALDeathCount() {
        return mHALDeathCount;
    }
}
+13 −14
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ import com.android.server.biometrics.sensors.ClientMonitor;
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.PerformanceTracker;
import com.android.server.biometrics.sensors.RemovalClient;

import org.json.JSONArray;
@@ -1171,6 +1172,9 @@ public class FaceService extends BiometricServiceBase {
    private native NativeHandle convertSurfaceToNativeHandle(Surface surface);

    private void dumpInternal(PrintWriter pw) {
        PerformanceTracker performanceTracker =
                PerformanceTracker.getInstanceForSensorId(getSensorId());

        JSONObject dump = new JSONObject();
        try {
            dump.put("service", "Face Manager");
@@ -1179,24 +1183,19 @@ public class FaceService extends BiometricServiceBase {
            for (UserInfo user : UserManager.get(getContext()).getUsers()) {
                final int userId = user.getUserHandle().getIdentifier();
                final int N = getBiometricUtils().getBiometricsForUser(getContext(), userId).size();
                PerformanceStats stats = mPerformanceMap.get(userId);
                PerformanceStats cryptoStats = mCryptoPerformanceMap.get(userId);
                JSONObject set = new JSONObject();
                set.put("id", userId);
                set.put("count", N);
                set.put("accept", (stats != null) ? stats.accept : 0);
                set.put("reject", (stats != null) ? stats.reject : 0);
                set.put("acquire", (stats != null) ? stats.acquire : 0);
                set.put("lockout", (stats != null) ? stats.lockout : 0);
                set.put("permanentLockout", (stats != null) ? stats.permanentLockout : 0);
                set.put("accept", performanceTracker.getAcceptForUser(userId));
                set.put("reject", performanceTracker.getRejectForUser(userId));
                set.put("acquire", performanceTracker.getAcquireForUser(userId));
                set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
                set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
                // cryptoStats measures statistics about secure face transactions
                // (e.g. to unlock password storage, make secure purchases, etc.)
                set.put("acceptCrypto", (cryptoStats != null) ? cryptoStats.accept : 0);
                set.put("rejectCrypto", (cryptoStats != null) ? cryptoStats.reject : 0);
                set.put("acquireCrypto", (cryptoStats != null) ? cryptoStats.acquire : 0);
                set.put("lockoutCrypto", (cryptoStats != null) ? cryptoStats.lockout : 0);
                set.put("permanentLockoutCrypto",
                        (cryptoStats != null) ? cryptoStats.permanentLockout : 0);
                set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
                set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
                set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
                sets.put(set);
            }

@@ -1205,7 +1204,7 @@ public class FaceService extends BiometricServiceBase {
            Slog.e(TAG, "dump formatting failure", e);
        }
        pw.println(dump);
        pw.println("HAL deaths since last reboot: " + mHALDeathCount);
        pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());

        mUsageStats.print(pw);
    }
+32 −36
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ 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.PerformanceTracker;
import com.android.server.biometrics.sensors.RemovalClient;

import org.json.JSONArray;
@@ -763,6 +764,9 @@ public class FingerprintService extends BiometricServiceBase {
    private native NativeHandle convertSurfaceToNativeHandle(Surface surface);

    private void dumpInternal(PrintWriter pw) {
        PerformanceTracker performanceTracker =
                PerformanceTracker.getInstanceForSensorId(getSensorId());

        JSONObject dump = new JSONObject();
        try {
            dump.put("service", "Fingerprint Manager");
@@ -771,24 +775,19 @@ public class FingerprintService extends BiometricServiceBase {
            for (UserInfo user : UserManager.get(getContext()).getUsers()) {
                final int userId = user.getUserHandle().getIdentifier();
                final int N = getBiometricUtils().getBiometricsForUser(getContext(), userId).size();
                PerformanceStats stats = mPerformanceMap.get(userId);
                PerformanceStats cryptoStats = mCryptoPerformanceMap.get(userId);
                JSONObject set = new JSONObject();
                set.put("id", userId);
                set.put("count", N);
                set.put("accept", (stats != null) ? stats.accept : 0);
                set.put("reject", (stats != null) ? stats.reject : 0);
                set.put("acquire", (stats != null) ? stats.acquire : 0);
                set.put("lockout", (stats != null) ? stats.lockout : 0);
                set.put("permanentLockout", (stats != null) ? stats.permanentLockout : 0);
                set.put("accept", performanceTracker.getAcceptForUser(userId));
                set.put("reject", performanceTracker.getRejectForUser(userId));
                set.put("acquire", performanceTracker.getAcquireForUser(userId));
                set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
                set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
                // cryptoStats measures statistics about secure fingerprint transactions
                // (e.g. to unlock password storage, make secure purchases, etc.)
                set.put("acceptCrypto", (cryptoStats != null) ? cryptoStats.accept : 0);
                set.put("rejectCrypto", (cryptoStats != null) ? cryptoStats.reject : 0);
                set.put("acquireCrypto", (cryptoStats != null) ? cryptoStats.acquire : 0);
                set.put("lockoutCrypto", (cryptoStats != null) ? cryptoStats.lockout : 0);
                set.put("permanentLockoutCrypto",
                    (cryptoStats != null) ? cryptoStats.permanentLockout : 0);
                set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
                set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
                set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
                sets.put(set);
            }

@@ -797,10 +796,13 @@ public class FingerprintService extends BiometricServiceBase {
            Slog.e(TAG, "dump formatting failure", e);
        }
        pw.println(dump);
        pw.println("HAL deaths since last reboot: " + mHALDeathCount);
        pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
    }

    private void dumpProto(FileDescriptor fd) {
        PerformanceTracker tracker =
                PerformanceTracker.getInstanceForSensorId(getSensorId());

        final ProtoOutputStream proto = new ProtoOutputStream(fd);
        for (UserInfo user : UserManager.get(getContext()).getUsers()) {
            final int userId = user.getUserHandle().getIdentifier();
@@ -812,34 +814,28 @@ public class FingerprintService extends BiometricServiceBase {
                    getBiometricUtils().getBiometricsForUser(getContext(), userId).size());

            // Normal fingerprint authentications (e.g. lockscreen)
            final PerformanceStats normal = mPerformanceMap.get(userId);
            if (normal != null) {
                final long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
                proto.write(PerformanceStatsProto.ACCEPT, normal.accept);
                proto.write(PerformanceStatsProto.REJECT, normal.reject);
                proto.write(PerformanceStatsProto.ACQUIRE, normal.acquire);
                proto.write(PerformanceStatsProto.LOCKOUT, normal.lockout);
                proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, normal.permanentLockout);
            long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
            proto.write(PerformanceStatsProto.ACCEPT, tracker.getAcceptForUser(userId));
            proto.write(PerformanceStatsProto.REJECT, tracker.getRejectForUser(userId));
            proto.write(PerformanceStatsProto.ACQUIRE, tracker.getAcquireForUser(userId));
            proto.write(PerformanceStatsProto.LOCKOUT, tracker.getTimedLockoutForUser(userId));
            proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT,
                    tracker.getPermanentLockoutForUser(userId));
            proto.end(countsToken);
            }

            // Statistics about secure fingerprint transactions (e.g. to unlock password
            // storage, make secure purchases, etc.)
            final PerformanceStats crypto = mCryptoPerformanceMap.get(userId);
            if (crypto != null) {
                final long countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
                proto.write(PerformanceStatsProto.ACCEPT, crypto.accept);
                proto.write(PerformanceStatsProto.REJECT, crypto.reject);
                proto.write(PerformanceStatsProto.ACQUIRE, crypto.acquire);
                proto.write(PerformanceStatsProto.LOCKOUT, crypto.lockout);
                proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, crypto.permanentLockout);
            countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
            proto.write(PerformanceStatsProto.ACCEPT, tracker.getAcceptCryptoForUser(userId));
            proto.write(PerformanceStatsProto.REJECT, tracker.getRejectCryptoForUser(userId));
            proto.write(PerformanceStatsProto.ACQUIRE, tracker.getAcquireCryptoForUser(userId));
            proto.write(PerformanceStatsProto.LOCKOUT, 0); // meaningless for crypto
            proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, 0); // meaningless for crypto
            proto.end(countsToken);
            }

            proto.end(userToken);
        }
        proto.flush();
        mPerformanceMap.clear();
        mCryptoPerformanceMap.clear();
        tracker.clear();
    }
}