FingerprintStats Westworld Migration
Adds 4 fingerprint atoms: NumFingerprints,
FingerprintAcquired, FingerprintAuthenticated, and
FingerprintErrorOccurred. Right now, these should have a 1-1 mapping
with dumpsys fingerprint. More acquire and error codes can be added
pending Metrics Council approval.
Note: I do not know how to test crypto fingerprints,
Note: Most of the logging likely fits better in BiometricService, but
given that it shares code with FaceService and we do not have approval
to log FaceService, I had to put the logic in FingerprintService.
Bug: b/113129470
Bug: b/113129210
Bug: b/113128442
Bug: b/72342203
Test: manually tested adding fingerprints and acquire/accept/reject/lockout with multiple users
Test: will make cts test
Change-Id: Ia8ca703bd4090f0f3612c6415785288517073117
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 786e76c9..c47f4f3 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -133,6 +133,9 @@
VibratorStateChanged vibrator_state_changed = 84;
DeferredJobStatsReported deferred_job_stats_reported = 85;
ThermalThrottlingStateChanged thermal_throttling = 86;
+ FingerprintAcquired fingerprint_acquired = 87;
+ FingerprintAuthenticated fingerprint_authenticated = 88;
+ FingerprintErrorOccurred fingerprint_error_occurred = 89;
}
// Pulled events will start at field 10000.
@@ -169,6 +172,7 @@
CategorySize category_size = 10028;
android.service.procstats.ProcessStatsSectionProto proc_stats = 10029;
BatteryVoltage battery_voltage = 10030;
+ NumFingerprints num_fingerprints = 10031;
}
// DO NOT USE field numbers above 100,000 in AOSP. Field numbers above
@@ -1832,6 +1836,60 @@
optional android.os.statsd.EventType event_id = 2;
}
+/**
+ * Logs when a fingerprint acquire event occurs.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+ */
+message FingerprintAcquired {
+ // The associated user. Eg: 0 for owners, 10+ for others.
+ // Defined in android/os/UserHandle.java
+ optional int32 user = 1;
+ // If this acquire is for a crypto fingerprint.
+ // e.g. Secure purchases, unlock password storage.
+ optional bool is_crypto = 2;
+}
+
+/**
+ * Logs when a fingerprint authentication event occurs.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+ */
+message FingerprintAuthenticated {
+ // The associated user. Eg: 0 for owners, 10+ for others.
+ // Defined in android/os/UserHandle.java
+ optional int32 user = 1;
+ // If this authentication is for a crypto fingerprint.
+ // e.g. Secure purchases, unlock password storage.
+ optional bool is_crypto = 2;
+ // Whether or not this authentication was successful.
+ optional bool is_authenticated = 3;
+}
+
+/**
+ * Logs when a fingerprint error occurs.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+ */
+message FingerprintErrorOccurred {
+ // The associated user. Eg: 0 for owners, 10+ for others.
+ // Defined in android/os/UserHandle.java
+ optional int32 user = 1;
+ // If this error is for a crypto fingerprint.
+ // e.g. Secure purchases, unlock password storage.
+ optional bool is_crypto = 2;
+
+ enum Error {
+ UNKNOWN = 0;
+ LOCKOUT = 1;
+ PERMANENT_LOCKOUT = 2;
+ }
+ // The type of error.
+ optional Error error = 3;
+}
//////////////////////////////////////////////////////////////////////
// Pulled atoms below this line //
//////////////////////////////////////////////////////////////////////
@@ -2408,3 +2466,16 @@
// Uses System.currentTimeMillis(), which is wall clock time.
optional int64 cache_time_millis = 3;
}
+
+/**
+ * Pulls the number of fingerprints for each user.
+ *
+ * Pulled from StatsCompanionService, which queries FingerprintManager.
+ */
+message NumFingerprints {
+ // The associated user. Eg: 0 for owners, 10+ for others.
+ // Defined in android/os/UserHandle.java
+ optional int32 user = 1;
+ // Number of fingerprints registered to that user.
+ optional int32 num_fingerprints = 2;
+}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 745ff74..c23d65e 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -217,6 +217,12 @@
{},
1 * NS_PER_SEC,
new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}},
+ // Number of fingerprints registered to each user.
+ {android::util::NUM_FINGERPRINTS,
+ {{},
+ {},
+ 1 * NS_PER_SEC,
+ new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS)}},
};
StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index a181b61..d019b6b 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -108,6 +108,8 @@
private ClientMonitor mPendingClient;
private PerformanceStats mPerformanceStats;
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.
@@ -715,6 +717,7 @@
pmap.put(mCurrentUserId, stats);
}
mPerformanceStats = stats;
+ mIsCrypto = (opId != 0);
startAuthentication(client, opPackageName);
});
@@ -847,7 +850,7 @@
return mKeyguardPackage.equals(clientPackage);
}
- private int getLockoutMode() {
+ protected int getLockoutMode() {
final int currentUser = ActivityManager.getCurrentUser();
final int failedAttempts = mFailedAttempts.get(currentUser, 0);
if (failedAttempts >= getFailedAttemptsLockoutPermanent()) {
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 95fb9e3..0a2e588 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -50,12 +50,14 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
+import android.util.StatsLog;
import android.util.proto.ProtoOutputStream;
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.AuthenticationClient;
import com.android.server.biometrics.BiometricService;
import com.android.server.biometrics.BiometricUtils;
import com.android.server.biometrics.ClientMonitor;
@@ -590,6 +592,11 @@
public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) {
mHandler.post(() -> {
FingerprintService.super.handleAcquired(deviceId, acquiredInfo, vendorCode);
+ if (getLockoutMode() == AuthenticationClient.LOCKOUT_NONE
+ && getCurrentClient() instanceof AuthenticationClient) {
+ // Ignore enrollment acquisitions or acquisitions when we are locked out.
+ StatsLog.write(StatsLog.FINGERPRINT_ACQUIRED, mCurrentUserId, mIsCrypto);
+ }
});
}
@@ -599,6 +606,22 @@
mHandler.post(() -> {
Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
FingerprintService.super.handleAuthenticated(fp, token);
+ // Send authentication to statsd.
+ final boolean authenticated = fingerId != 0;
+ StatsLog.write(StatsLog.FINGERPRINT_AUTHENTICATED, mCurrentUserId, mIsCrypto,
+ authenticated);
+ if (!authenticated) {
+ // If we failed to authenticate because of a lockout, inform statsd.
+ final int lockoutMode = getLockoutMode();
+ if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) {
+ StatsLog.write(StatsLog.FINGERPRINT_ERROR_OCCURRED, mCurrentUserId,
+ mIsCrypto, StatsLog.FINGERPRINT_ERROR_OCCURRED__ERROR__LOCKOUT);
+ } else if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
+ StatsLog.write(StatsLog.FINGERPRINT_ERROR_OCCURRED, mCurrentUserId,
+ mIsCrypto,
+ StatsLog.FINGERPRINT_ERROR_OCCURRED__ERROR__PERMANENT_LOCKOUT);
+ }
+ }
});
}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index a4d42a1..444ac2c 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -32,6 +32,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.hardware.fingerprint.FingerprintManager;
import android.net.NetworkStats;
import android.net.wifi.IWifiManager;
import android.net.wifi.WifiActivityEnergyInfo;
@@ -1171,6 +1172,28 @@
}
}
+ private void pullNumFingerprints(int tagId, List<StatsLogEventWrapper> pulledData) {
+ FingerprintManager fingerprintManager = mContext.getSystemService(FingerprintManager.class);
+ if (fingerprintManager == null) {
+ return;
+ }
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ if (userManager == null) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ long elapsedNanos = SystemClock.elapsedRealtimeNanos();
+ for (UserInfo user : userManager.getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+ final int numFingerprints = fingerprintManager.getEnrolledFingerprints(userId).size();
+ StatsLogEventWrapper e = new StatsLogEventWrapper(elapsedNanos, tagId, 2 /* fields */);
+ e.writeInt(userId);
+ e.writeInt(numFingerprints);
+ pulledData.add(e);
+ }
+ Binder.restoreCallingIdentity(token);
+ }
+
/**
* Pulls various data.
*/
@@ -1277,6 +1300,10 @@
pullCategorySize(tagId, ret);
break;
}
+ case StatsLog.NUM_FINGERPRINTS: {
+ pullNumFingerprints(tagId, ret);
+ break;
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;