Add logging for Biometrics

Bug: 117060268
Bug: 120161047

Test: With LoggableMonitor DEBUG turned on, manually tested and checked
      logs

Change-Id: Ib8b3ec9ed7913e38b17597ac7eda535925e1576a
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
index eaa7a83..bd4acdb 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.security.KeyStore;
@@ -71,6 +72,11 @@
         stop(false /* initiatedByClient */);
     }
 
+    @Override
+    protected int statsAction() {
+        return BiometricsProtoEnums.ACTION_AUTHENTICATE;
+    }
+
     public boolean isBiometricPrompt() {
         return getCookie() != 0;
     }
@@ -80,8 +86,16 @@
     }
 
     @Override
+    protected boolean isCryptoOperation() {
+        return mOpId != 0;
+    }
+
+    @Override
     public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,
             boolean authenticated, ArrayList<Byte> token) {
+        super.logOnAuthenticated(authenticated, mRequireConfirmation, getTargetUserId(),
+                isBiometricPrompt());
+
         final BiometricServiceBase.ServiceListener listener = getListener();
 
         mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated);
@@ -142,10 +156,7 @@
                     final int errorCode = lockoutMode == LOCKOUT_TIMED
                             ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
                             : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
-                    if (listener != null) {
-                        listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */,
-                                getCookie());
-                    }
+                    onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
                 } else {
                     // Don't send onAuthenticationFailed if we're in lockout, it causes a
                     // janky UI on Keyguard/BiometricPrompt since "authentication failed"
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 15d66e6..3ee1a04 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -41,6 +41,7 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -64,6 +65,7 @@
 import android.text.TextUtils;
 import android.util.Pair;
 import android.util.Slog;
+import android.util.StatsLog;
 
 import com.android.internal.R;
 import com.android.internal.statusbar.IStatusBarService;
@@ -309,6 +311,7 @@
             // Continue authentication with the same modality/modalities after "try again" is
             // pressed
             final int mModality;
+            final boolean mRequireConfirmation;
 
             // The current state, which can be either idle, called, or started
             private int mState = STATE_AUTH_IDLE;
@@ -316,10 +319,13 @@
             // the authentication.
             byte[] mTokenEscrow;
 
+            // Timestamp when hardware authentication occurred
+            private long mAuthenticatedTimeMs;
+
             AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId,
                     int userId, IBiometricServiceReceiver receiver, String opPackageName,
                     Bundle bundle, int callingUid, int callingPid, int callingUserId,
-                    int modality) {
+                    int modality, boolean requireConfirmation) {
                 mModalitiesWaiting = modalities;
                 mToken = token;
                 mSessionId = sessionId;
@@ -331,6 +337,11 @@
                 mCallingPid = callingPid;
                 mCallingUserId = callingUserId;
                 mModality = modality;
+                mRequireConfirmation = requireConfirmation;
+            }
+
+            boolean isCrypto() {
+                return mSessionId != 0;
             }
 
             boolean containsCookie(int cookie) {
@@ -412,6 +423,7 @@
                         mCurrentAuthSession.mState = STATE_AUTH_IDLE;
                         mCurrentAuthSession = null;
                     } else {
+                        mCurrentAuthSession.mAuthenticatedTimeMs = System.currentTimeMillis();
                         // Store the auth token and submit it to keystore after the confirmation
                         // button has been pressed.
                         mCurrentAuthSession.mTokenEscrow = token;
@@ -557,6 +569,8 @@
                     return;
                 }
 
+                logDialogDismissed(reason);
+
                 if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) {
                     // Positive button is used by passive modalities as a "confirm" button,
                     // do not send to client
@@ -599,6 +613,77 @@
                             mCurrentAuthSession.mModality);
                 });
             }
+
+            private void logDialogDismissed(int reason) {
+                if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
+                    // Explicit auth, authentication confirmed.
+                    // Latency in this case is authenticated -> confirmed. <Biometric>Service
+                    // should have the first half (first acquired -> authenticated).
+                    final long latency = System.currentTimeMillis()
+                            - mCurrentAuthSession.mAuthenticatedTimeMs;
+
+                    if (LoggableMonitor.DEBUG) {
+                        Slog.v(LoggableMonitor.TAG, "Confirmed! Modality: " + statsModality()
+                                + ", User: " + mCurrentAuthSession.mUserId
+                                + ", IsCrypto: " + mCurrentAuthSession.isCrypto()
+                                + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+                                + ", RequireConfirmation: "
+                                    + mCurrentAuthSession.mRequireConfirmation
+                                + ", State: " + StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED
+                                + ", Latency: " + latency);
+                    }
+
+                    StatsLog.write(StatsLog.BIOMETRIC_AUTHENTICATED,
+                            statsModality(),
+                            mCurrentAuthSession.mUserId,
+                            mCurrentAuthSession.isCrypto(),
+                            BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
+                            mCurrentAuthSession.mRequireConfirmation,
+                            StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
+                            latency);
+                } else {
+                    int error = reason == BiometricPrompt.DISMISSED_REASON_NEGATIVE
+                            ? BiometricConstants.BIOMETRIC_ERROR_NEGATIVE_BUTTON
+                            : reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL
+                                    ? BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED
+                                    : 0;
+                    if (LoggableMonitor.DEBUG) {
+                        Slog.v(LoggableMonitor.TAG, "Dismissed! Modality: " + statsModality()
+                                + ", User: " + mCurrentAuthSession.mUserId
+                                + ", IsCrypto: " + mCurrentAuthSession.isCrypto()
+                                + ", Action: " + BiometricsProtoEnums.ACTION_AUTHENTICATE
+                                + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+                                + ", Error: " + error);
+                    }
+                    // Auth canceled
+                    StatsLog.write(StatsLog.BIOMETRIC_ERROR_OCCURRED,
+                            statsModality(),
+                            mCurrentAuthSession.mUserId,
+                            mCurrentAuthSession.isCrypto(),
+                            BiometricsProtoEnums.ACTION_AUTHENTICATE,
+                            BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
+                            error,
+                            0 /* vendorCode */);
+                }
+            }
+
+            private int statsModality() {
+                int modality = 0;
+                if (mCurrentAuthSession == null) {
+                    return BiometricsProtoEnums.MODALITY_UNKNOWN;
+                }
+                if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FINGERPRINT)
+                        != 0) {
+                    modality |= BiometricsProtoEnums.MODALITY_FINGERPRINT;
+                }
+                if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_IRIS) != 0) {
+                    modality |= BiometricsProtoEnums.MODALITY_IRIS;
+                }
+                if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FACE) != 0) {
+                    modality |= BiometricsProtoEnums.MODALITY_FACE;
+                }
+                return modality;
+            }
         };
 
         @Override // Binder call
@@ -815,7 +900,11 @@
             try {
                 boolean requireConfirmation = bundle.getBoolean(
                         BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
-
+                if ((modality & TYPE_FACE) != 0) {
+                    // Check if the user has forced confirmation to be required in Settings.
+                    requireConfirmation = requireConfirmation
+                            || mSettingObserver.getFaceAlwaysRequireConfirmation();
+                }
                 // Generate random cookies to pass to the services that should prepare to start
                 // authenticating. Store the cookie here and wait for all services to "ack"
                 // with the cookie. Once all cookies are received, we can show the prompt
@@ -827,7 +916,7 @@
                 authenticators.put(modality, cookie);
                 mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId,
                         receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
-                        modality);
+                        modality, requireConfirmation);
                 mPendingAuthSession.mState = STATE_AUTH_CALLED;
                 // No polymorphism :(
                 if ((modality & TYPE_FINGERPRINT) != 0) {
@@ -839,9 +928,6 @@
                     Slog.w(TAG, "Iris unsupported");
                 }
                 if ((modality & TYPE_FACE) != 0) {
-                    // Check if the user has forced confirmation to be required in Settings.
-                    requireConfirmation = requireConfirmation
-                            || mSettingObserver.getFaceAlwaysRequireConfirmation();
                     mFaceService.prepareForAuthentication(requireConfirmation,
                             token, sessionId, userId, mInternalReceiver, opPackageName,
                             cookie, callingUid, callingPid, callingUserId);
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 2791165..b65535a 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -35,6 +35,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
@@ -55,6 +56,7 @@
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
+import android.util.StatsLog;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
@@ -220,8 +222,16 @@
      */
     protected void notifyClientActiveCallbacks(boolean isActive) {}
 
+    protected abstract int statsModality();
+
     protected abstract class AuthenticationClientImpl extends AuthenticationClient {
 
+        // Used to check if the public API that was invoked was from FingerprintManager. Only
+        // to be overridden by FingerprintService.
+        protected boolean isFingerprint() {
+            return false;
+        }
+
         public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
                 IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId,
                 boolean restricted, String owner, int cookie, boolean requireConfirmation) {
@@ -230,6 +240,19 @@
         }
 
         @Override
+        protected int statsClient() {
+            if (isKeyguard(getOwnerString())) {
+                return BiometricsProtoEnums.CLIENT_KEYGUARD;
+            } else if (isBiometricPrompt()) {
+                return BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT;
+            } else if (isFingerprint()) {
+                return BiometricsProtoEnums.CLIENT_FINGERPRINT_MANAGER;
+            } else {
+                return BiometricsProtoEnums.CLIENT_UNKNOWN;
+            }
+        }
+
+        @Override
         public void onStart() {
             try {
                 mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
@@ -296,7 +319,7 @@
         }
     }
 
-    protected class RemovalClientImpl extends RemovalClient {
+    protected abstract class RemovalClientImpl extends RemovalClient {
         private boolean mShouldNotify;
 
         public RemovalClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
@@ -318,7 +341,7 @@
         }
     }
 
-    protected class EnumerateClientImpl extends EnumerateClient {
+    protected abstract class EnumerateClientImpl extends EnumerateClient {
 
         public EnumerateClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
                 IBinder token, ServiceListener listener, int groupId, int userId,
@@ -600,6 +623,8 @@
         mHALDeathCount++;
         handleError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
                 0 /*vendorCode */);
+
+        StatsLog.write(StatsLog.BIOMETRIC_HAL_DEATH_REPORTED, statsModality());
     }
 
     protected ClientMonitor getCurrentClient() {
@@ -653,7 +678,6 @@
             } else {
                 updateActiveGroup(mCurrentUserId, null);
             }
-
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/ClientMonitor.java b/services/core/java/com/android/server/biometrics/ClientMonitor.java
index d19aff6..e80b39b 100644
--- a/services/core/java/com/android/server/biometrics/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/ClientMonitor.java
@@ -36,7 +36,7 @@
  * the current client.  Subclasses are responsible for coordinating the interaction with
  * the biometric's HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.).
  */
-public abstract class ClientMonitor implements IBinder.DeathRecipient {
+public abstract class ClientMonitor extends LoggableMonitor implements IBinder.DeathRecipient {
     protected static final int ERROR_ESRCH = 3; // Likely HAL is dead. See errno.h.
     protected static final boolean DEBUG = BiometricServiceBase.DEBUG;
     private static final AudioAttributes FINGERPRINT_SONFICATION_ATTRIBUTES =
@@ -157,6 +157,7 @@
      * @return true if client should be removed
      */
     public boolean onAcquired(int acquiredInfo, int vendorCode) {
+        super.logOnAcquired(acquiredInfo, vendorCode, getTargetUserId());
         try {
             if (mListener != null) {
                 mListener.onAcquired(getHalDeviceId(), acquiredInfo, vendorCode);
@@ -180,6 +181,7 @@
      * @return true if client should be removed
      */
     public boolean onError(long deviceId, int error, int vendorCode) {
+        super.logOnError(error, vendorCode, getTargetUserId());
         try {
             if (mListener != null) {
                 mListener.onError(deviceId, error, vendorCode, getCookie());
diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java
index 3ff94bc..1124b7f 100644
--- a/services/core/java/com/android/server/biometrics/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/EnrollClient.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -51,6 +52,11 @@
     }
 
     @Override
+    protected int statsAction() {
+        return BiometricsProtoEnums.ACTION_ENROLL;
+    }
+
+    @Override
     public boolean onEnrollResult(BiometricAuthenticator.Identifier identifier,
             int remaining) {
         if (remaining == 0) {
diff --git a/services/core/java/com/android/server/biometrics/EnumerateClient.java b/services/core/java/com/android/server/biometrics/EnumerateClient.java
index df6220c..0f57f48 100644
--- a/services/core/java/com/android/server/biometrics/EnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/EnumerateClient.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -38,6 +39,11 @@
     }
 
     @Override
+    protected int statsAction() {
+        return BiometricsProtoEnums.ACTION_ENUMERATE;
+    }
+
+    @Override
     public int start() {
         // The biometric template ids will be removed when we get confirmation from the HAL
         try {
diff --git a/services/core/java/com/android/server/biometrics/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/LoggableMonitor.java
new file mode 100644
index 0000000..91c924d
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/LoggableMonitor.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.face.FaceManager;
+import android.util.Slog;
+import android.util.StatsLog;
+
+/**
+ * Abstract class that adds logging functionality to the ClientMonitor classes.
+ */
+public abstract class LoggableMonitor {
+
+    public static final String TAG = "BiometricStats";
+    public static final boolean DEBUG = true;
+
+    private long mFirstAcquireTimeMs;
+
+    /**
+     * Only valid for AuthenticationClient.
+     * @return true if the client is authenticating for a crypto operation.
+     */
+    protected boolean isCryptoOperation() {
+        return false;
+    }
+
+    /**
+     * @return One of {@link BiometricsProtoEnums} MODALITY_* constants.
+     */
+    protected abstract int statsModality();
+
+    /**
+     * Action == enroll, authenticate, remove, enumerate.
+     * @return One of {@link BiometricsProtoEnums} ACTION_* constants.
+     */
+    protected abstract int statsAction();
+
+    /**
+     * Only matters for AuthenticationClient. Should only be overridden in
+     * {@link BiometricServiceBase}, which determines if a client is for BiometricPrompt, Keyguard,
+     * etc.
+     * @return one of {@link BiometricsProtoEnums} CLIENT_* constants.
+     */
+    protected int statsClient() {
+        return BiometricsProtoEnums.CLIENT_UNKNOWN;
+    }
+
+    protected final void logOnAcquired(int acquiredInfo, int vendorCode, int targetUserId) {
+        if (statsModality() == BiometricsProtoEnums.MODALITY_FACE) {
+            if (acquiredInfo == FaceManager.FACE_ACQUIRED_START) {
+                mFirstAcquireTimeMs = System.currentTimeMillis();
+            }
+        } else if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
+            if (mFirstAcquireTimeMs == 0) {
+                mFirstAcquireTimeMs = System.currentTimeMillis();
+            }
+        }
+        if (DEBUG) {
+            Slog.v(TAG, "Acquired! Modality: " + statsModality()
+                    + ", User: " + targetUserId
+                    + ", IsCrypto: " + isCryptoOperation()
+                    + ", Action: " + statsAction()
+                    + ", Client: " + statsClient()
+                    + ", AcquiredInfo: " + acquiredInfo
+                    + ", VendorCode: " + vendorCode);
+        }
+        StatsLog.write(StatsLog.BIOMETRIC_ACQUIRED,
+                statsModality(),
+                targetUserId,
+                isCryptoOperation(),
+                statsAction(),
+                statsClient(),
+                acquiredInfo,
+                0 /* vendorCode */); // Don't log vendorCode for now
+    }
+
+    protected final void logOnError(int error, int vendorCode, int targetUserId) {
+        if (DEBUG) {
+            Slog.v(TAG, "Error! Modality: " + statsModality()
+                    + ", User: " + targetUserId
+                    + ", IsCrypto: " + isCryptoOperation()
+                    + ", Action: " + statsAction()
+                    + ", Client: " + statsClient()
+                    + ", Error: " + error
+                    + ", VendorCode: " + vendorCode);
+        }
+        StatsLog.write(StatsLog.BIOMETRIC_ERROR_OCCURRED,
+                statsModality(),
+                targetUserId,
+                isCryptoOperation(),
+                statsAction(),
+                statsClient(),
+                error,
+                vendorCode);
+    }
+
+    protected final void logOnAuthenticated(boolean authenticated, boolean requireConfirmation,
+            int targetUserId, boolean isBiometricPrompt) {
+        int authState = StatsLog.BIOMETRIC_AUTHENTICATED__STATE__UNKNOWN;
+        if (!authenticated) {
+            authState = StatsLog.BIOMETRIC_AUTHENTICATED__STATE__REJECTED;
+        } else {
+            // Authenticated
+            if (isBiometricPrompt && requireConfirmation) {
+                authState = StatsLog.BIOMETRIC_AUTHENTICATED__STATE__PENDING_CONFIRMATION;
+            } else {
+                authState = StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED;
+            }
+        }
+
+        // Only valid if we have a first acquired time, otherwise set to -1
+        final long latency = mFirstAcquireTimeMs != 0
+                ? (System.currentTimeMillis() - mFirstAcquireTimeMs)
+                : -1;
+
+        if (DEBUG) {
+            Slog.v(TAG, "Authenticated! Modality: " + statsModality()
+                    + ", User: " + targetUserId
+                    + ", IsCrypto: " + isCryptoOperation()
+                    + ", Client: " + statsClient()
+                    + ", RequireConfirmation: " + requireConfirmation
+                    + ", State: " + authState
+                    + ", Latency: " + latency);
+        }
+
+        StatsLog.write(StatsLog.BIOMETRIC_AUTHENTICATED,
+                statsModality(),
+                targetUserId,
+                isCryptoOperation(),
+                statsClient(),
+                requireConfirmation,
+                authState,
+                latency);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/RemovalClient.java b/services/core/java/com/android/server/biometrics/RemovalClient.java
index be233ec..0509067 100644
--- a/services/core/java/com/android/server/biometrics/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/RemovalClient.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -43,6 +44,11 @@
     }
 
     @Override
+    protected int statsAction() {
+        return BiometricsProtoEnums.ACTION_REMOVE;
+    }
+
+    @Override
     public int start() {
         // The biometric template ids will be removed when we get confirmation from the HAL
         try {
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index d4be539..90342ee 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -27,6 +27,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
@@ -90,6 +91,11 @@
             super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
                     restricted, owner, cookie, requireConfirmation);
         }
+
+        @Override
+        protected int statsModality() {
+            return FaceService.this.statsModality();
+        }
     }
 
     /**
@@ -126,6 +132,11 @@
                 public boolean shouldVibrate() {
                     return false;
                 }
+
+                @Override
+                protected int statsModality() {
+                    return FaceService.this.statsModality();
+                }
             };
 
             enrollInternal(client, UserHandle.getCallingUserId());
@@ -206,7 +217,12 @@
             final boolean restricted = isRestricted();
             final RemovalClientImpl client = new RemovalClientImpl(getContext(), mDaemonWrapper,
                     mHalDeviceId, token, new ServiceListenerImpl(receiver), faceId, 0 /* groupId */,
-                    userId, restricted, token.toString());
+                    userId, restricted, token.toString()) {
+                @Override
+                protected int statsModality() {
+                    return FaceService.this.statsModality();
+                }
+            };
             client.setShouldNotifyUserActivity(true);
             removeInternal(client);
         }
@@ -219,7 +235,12 @@
             final boolean restricted = isRestricted();
             final EnumerateClientImpl client = new EnumerateClientImpl(getContext(), mDaemonWrapper,
                     mHalDeviceId, token, new ServiceListenerImpl(receiver), userId, userId,
-                    restricted, getContext().getOpPackageName());
+                    restricted, getContext().getOpPackageName()) {
+                @Override
+                protected int statsModality() {
+                    return FaceService.this.statsModality();
+                }
+            };
             enumerateInternal(client);
         }
 
@@ -770,6 +791,11 @@
         // noop for Face.
     }
 
+    @Override
+    protected int statsModality() {
+        return BiometricsProtoEnums.MODALITY_FACE;
+    }
+
     /** Gets the face daemon */
     private synchronized IBiometricsFace getFaceDaemon() {
         if (mDaemon == null) {
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 f84cda03..62947c7 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -30,6 +30,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
@@ -101,6 +102,11 @@
     }
 
     private final class FingerprintAuthClient extends AuthenticationClientImpl {
+        @Override
+        protected boolean isFingerprint() {
+            return true;
+        }
+
         public FingerprintAuthClient(Context context,
                 DaemonWrapper daemon, long halDeviceId, IBinder token,
                 ServiceListener listener, int targetUserId, int groupId, long opId,
@@ -109,6 +115,11 @@
             super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
                     restricted, owner, cookie, requireConfirmation);
         }
+
+        @Override
+        protected int statsModality() {
+            return FingerprintService.this.statsModality();
+        }
     }
 
     /**
@@ -147,6 +158,11 @@
                 public boolean shouldVibrate() {
                     return true;
                 }
+
+                @Override
+                protected int statsModality() {
+                    return FingerprintService.this.statsModality();
+                }
             };
 
             enrollInternal(client, userId);
@@ -225,7 +241,12 @@
             final boolean restricted = isRestricted();
             final RemovalClientImpl client = new RemovalClientImpl(getContext(), mDaemonWrapper,
                     mHalDeviceId, token, new ServiceListenerImpl(receiver), fingerId, groupId,
-                    userId, restricted, token.toString());
+                    userId, restricted, token.toString()) {
+                @Override
+                protected int statsModality() {
+                    return FingerprintService.this.statsModality();
+                }
+            };
             client.setShouldNotifyUserActivity(true);
             removeInternal(client);
         }
@@ -238,7 +259,12 @@
             final boolean restricted = isRestricted();
             final EnumerateClientImpl client = new EnumerateClientImpl(getContext(), mDaemonWrapper,
                     mHalDeviceId, token, new ServiceListenerImpl(receiver), userId, userId,
-                    restricted, getContext().getOpPackageName());
+                    restricted, getContext().getOpPackageName()) {
+                @Override
+                protected int statsModality() {
+                    return FingerprintService.this.statsModality();
+                }
+            };
             enumerateInternal(client);
         }
 
@@ -544,6 +570,11 @@
             }
             return remaining == 0;
         }
+
+        @Override
+        protected int statsModality() {
+            return FingerprintService.this.statsModality();
+        }
     }
 
     /**
@@ -558,6 +589,11 @@
                     restricted,
                     owner);
         }
+
+        @Override
+        protected int statsModality() {
+            return FingerprintService.this.statsModality();
+        }
     }
 
     private final FingerprintMetrics mFingerprintMetrics = new FingerprintMetrics();
@@ -885,6 +921,11 @@
         }
     }
 
+    @Override
+    protected int statsModality() {
+        return BiometricsProtoEnums.MODALITY_FINGERPRINT;
+    }
+
     /** Gets the fingerprint daemon */
     private synchronized IBiometricsFingerprint getFingerprintDaemon() {
         if (mDaemon == null) {
diff --git a/services/core/java/com/android/server/biometrics/iris/IrisService.java b/services/core/java/com/android/server/biometrics/iris/IrisService.java
index 37cdc2a..eb457b6 100644
--- a/services/core/java/com/android/server/biometrics/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/iris/IrisService.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.iris;
 
 import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
 
 import com.android.server.biometrics.BiometricServiceBase;
 import com.android.server.biometrics.BiometricUtils;
@@ -128,4 +129,9 @@
     protected boolean checkAppOps(int uid, String opPackageName) {
         return false;
     }
+
+    @Override
+    protected int statsModality() {
+        return BiometricsProtoEnums.MODALITY_IRIS;
+    }
 }