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;