Merge "9/n: Add BiometricScheduler proto dump"
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index fcdf61e..c854ac98 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -38,7 +38,7 @@
     SensorPropertiesInternal getSensorProperties(String opPackageName);
 
     // Requests a proto dump of the sensor. See biometrics.proto
-    byte[] dumpSensorServiceStateProto();
+    byte[] dumpSensorServiceStateProto(boolean clearSchedulerBuffer);
 
     // This method prepares the service to start authenticating, but doesn't start authentication.
     // This is protected by the MANAGE_BIOMETRIC signature permission. This method should only be
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 3b19f12..1b188e8 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -35,7 +35,7 @@
     ITestSession createTestSession(int sensorId, String opPackageName);
 
     // Requests a proto dump of the specified sensor
-    byte[] dumpSensorServiceStateProto(int sensorId);
+    byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
 
     // Retrieve static sensor properties for all face sensors
     List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 74c5b58..3657a83 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -36,7 +36,7 @@
     ITestSession createTestSession(int sensorId, String opPackageName);
 
     // Requests a proto dump of the specified sensor
-    byte[] dumpSensorServiceStateProto(int sensorId);
+    byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
 
     // Retrieve static sensor properties for all fingerprint sensors
     List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
diff --git a/core/proto/android/server/biometrics.proto b/core/proto/android/server/biometrics.proto
index 14b5c52..900235e 100644
--- a/core/proto/android/server/biometrics.proto
+++ b/core/proto/android/server/biometrics.proto
@@ -120,8 +120,8 @@
 
     optional Modality modality = 2;
 
-    // State of the sensor's scheduler. True if currently handling an operation, false if idle.
-    optional bool is_busy = 3;
+    // State of the sensor's scheduler.
+    optional BiometricSchedulerProto scheduler = 3;
 
     // User states for this sensor.
     repeated UserStateProto user_states = 4;
@@ -136,4 +136,39 @@
 
     // Number of fingerprints enrolled
     optional int32 num_enrolled = 2;
+}
+
+// BiometricScheduler dump
+message BiometricSchedulerProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    // Operation currently being handled by the BiometricScheduler
+    optional ClientMonitorEnum current_operation = 1;
+
+    // Total number of operations that have been handled, not including the current one if one
+    // exists. Kept in FIFO order (most recent at the end of the array)
+    optional int32 total_operations = 2;
+
+    // A list of recent past operations in the order which they were handled
+    repeated ClientMonitorEnum recent_operations = 3;
+}
+
+// BaseClientMonitor subtypes
+enum ClientMonitorEnum {
+    CM_NONE = 0;
+    CM_UPDATE_ACTIVE_USER = 1;
+    CM_ENROLL = 2;
+    CM_AUTHENTICATE = 3;
+    CM_REMOVE = 4;
+    CM_GET_AUTHENTICATOR_ID = 5;
+    CM_ENUMERATE = 6;
+    CM_INTERNAL_CLEANUP = 7;
+    CM_SET_FEATURE = 8;
+    CM_GET_FEATURE = 9;
+    CM_GENERATE_CHALLENGE = 10;
+    CM_REVOKE_CHALLENGE = 11;
+    CM_RESET_LOCKOUT = 12;
+    CM_DETECT_INTERACTION = 13;
+    CM_INVALIDATION_REQUESTER = 14;
+    CM_INVALIDATE = 15;
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 52152ab..14292d9c 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -37,7 +37,6 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -62,8 +61,6 @@
     private static final String TAG = "BiometricService/AuthSession";
     private static final boolean DEBUG = false;
 
-
-
     /*
      * Defined in biometrics.proto
      */
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 3387049..614c5f1 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -815,12 +815,16 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 if (args.length > 0 && "--proto".equals(args[0])) {
+                    final boolean clearSchedulerBuffer = args.length > 1
+                            && "--clear-scheduler-buffer".equals(args[1]);
+                    Slog.d(TAG, "ClearSchedulerBuffer: " + clearSchedulerBuffer);
                     final ProtoOutputStream proto = new ProtoOutputStream(fd);
                     proto.write(BiometricServiceStateProto.AUTH_SESSION_STATE,
                             mCurrentAuthSession != null ? mCurrentAuthSession.getState()
                                     : STATE_AUTH_IDLE);
                     for (BiometricSensor sensor : mSensors) {
-                        byte[] serviceState = sensor.impl.dumpSensorServiceStateProto();
+                        byte[] serviceState = sensor.impl
+                                .dumpSensorServiceStateProto(clearSchedulerBuffer);
                         proto.write(BiometricServiceStateProto.SENSOR_SERVICE_STATES, serviceState);
                     }
                     proto.flush();
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 5663495..3f6ae64 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -33,6 +33,7 @@
 import android.util.EventLog;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.Utils;
 
 import java.util.ArrayList;
@@ -298,4 +299,9 @@
             mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
         }
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_AUTHENTICATE;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index f3c37ef..8fa3bbb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -24,6 +24,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.NoSuchElementException;
 
 /**
@@ -80,6 +82,12 @@
     @NonNull protected Callback mCallback;
 
     /**
+     * Returns a ClientMonitorEnum constant defined in biometrics.proto
+     * @return
+     */
+    public abstract int getProtoEnum();
+
+    /**
      * @param context    system_server context
      * @param token      a unique token for the client
      * @param listener   recipient of related events (e.g. authentication)
@@ -195,10 +203,16 @@
         return mSensorId;
     }
 
+    @VisibleForTesting
+    public Callback getCallback() {
+        return mCallback;
+    }
+
     @Override
     public String toString() {
         return "{[" + mSequentialId + "] "
                 + this.getClass().getSimpleName()
+                + ", " + getProtoEnum()
                 + ", " + getOwnerString()
                 + ", " + getCookie() + "}";
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index aa7faf5..c86bfcb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -28,8 +28,11 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.BiometricSchedulerProto;
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import java.io.PrintWriter;
@@ -51,6 +54,8 @@
 public class BiometricScheduler {
 
     private static final String BASE_TAG = "BiometricScheduler";
+    // Number of recent operations to keep in our logs for dumpsys
+    private static final int LOG_NUM_RECENT_OPERATIONS = 50;
 
     /**
      * Contains all the necessary information for a HAL operation.
@@ -200,6 +205,10 @@
     @VisibleForTesting @Nullable Operation mCurrentOperation;
     @NonNull private final ArrayDeque<CrashState> mCrashStates;
 
+    private int mTotalOperationsHandled;
+    private final int mRecentOperationsLimit;
+    @NonNull private final List<Integer> mRecentOperations;
+
     // Internal callback, notified when an operation is complete. Notifies the requester
     // that the operation is complete, before performing internal scheduler work (such as
     // starting the next client).
@@ -240,7 +249,12 @@
                             mCurrentOperation.mClientMonitor.getSensorId(), false /* active */);
                 }
 
+                if (mRecentOperations.size() >= mRecentOperationsLimit) {
+                    mRecentOperations.remove(0);
+                }
+                mRecentOperations.add(mCurrentOperation.mClientMonitor.getProtoEnum());
                 mCurrentOperation = null;
+                mTotalOperationsHandled++;
                 startNextOperationIfIdle();
             });
         }
@@ -249,13 +263,15 @@
     @VisibleForTesting
     BiometricScheduler(@NonNull String tag,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            @NonNull IBiometricService biometricService) {
+            @NonNull IBiometricService biometricService, int recentOperationsLimit) {
         mBiometricTag = tag;
         mInternalCallback = new InternalCallback();
         mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
         mPendingOperations = new ArrayDeque<>();
         mBiometricService = biometricService;
         mCrashStates = new ArrayDeque<>();
+        mRecentOperationsLimit = recentOperationsLimit;
+        mRecentOperations = new ArrayList<>();
     }
 
     /**
@@ -267,7 +283,7 @@
     public BiometricScheduler(@NonNull String tag,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
         this(tag, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
-                ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+                ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS);
     }
 
     /**
@@ -602,6 +618,24 @@
         }
     }
 
+    public byte[] dumpProtoState(boolean clearSchedulerBuffer) {
+        final ProtoOutputStream proto = new ProtoOutputStream();
+        proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null
+                ? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE);
+        proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled);
+        Slog.d(getTag(), "Total operations: " + mTotalOperationsHandled);
+        for (int i = 0; i < mRecentOperations.size(); i++) {
+            Slog.d(getTag(), "Operation: " + mRecentOperations.get(i));
+            proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, mRecentOperations.get(i));
+        }
+        proto.flush();
+
+        if (clearSchedulerBuffer) {
+            mRecentOperations.clear();
+        }
+        return proto.getBytes();
+    }
+
     /**
      * Clears the scheduler of anything work-related. This should be used for example when the
      * HAL dies.
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 8bf9680..8d81016 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -24,6 +24,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
+
 import java.util.Arrays;
 
 /**
@@ -106,4 +108,9 @@
                 false /* enrollSuccessful */);
         super.onError(error, vendorCode);
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_ENROLL;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
index 6a622c3..741946e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
+
 public abstract class GenerateChallengeClient<T> extends HalClientMonitor<T> {
 
     private static final String TAG = "GenerateChallengeClient";
@@ -50,4 +52,9 @@
 
         startHalOperation();
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_GENERATE_CHALLENGE;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 8529e81..ce24e5e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -24,6 +24,7 @@
 import android.util.Slog;
 
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.BiometricsProto;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -166,4 +167,9 @@
         }
         ((EnumerateConsumer) mCurrentTask).onEnumerationResult(identifier, remaining);
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_INTERNAL_CLEANUP;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 2693f2f..9d19fdf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -24,6 +24,7 @@
 import android.util.Slog;
 
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.BiometricsProto;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -123,4 +124,9 @@
     public List<BiometricAuthenticator.Identifier> getUnknownHALTemplates() {
         return mUnknownHALTemplates;
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_ENUMERATE;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
index 630e5ea..cede4a7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
@@ -24,6 +24,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
+
 import java.util.Map;
 
 /**
@@ -70,4 +72,9 @@
     public void unableToStart() {
 
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_INVALIDATE;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
index c97003b..5ba1b00 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
@@ -23,6 +23,8 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IInvalidationCallback;
 
+import com.android.server.biometrics.BiometricsProto;
+
 /**
  * ClientMonitor subclass responsible for coordination of authenticatorId invalidation of other
  * sensors. See {@link InvalidationClient} for the ClientMonitor subclass responsible for initiating
@@ -89,4 +91,9 @@
         mBiometricManager.invalidateAuthenticatorIds(getTargetUserId(), getSensorId(),
                 mInvalidationCallback);
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_INVALIDATION_REQUESTER;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 4ea48fd..e062695 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -25,6 +25,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
+
 import java.util.Map;
 
 /**
@@ -90,4 +92,9 @@
             mCallback.onClientFinished(this, true /* success */);
         }
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_REMOVE;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
index 187193d..90fa1b4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
@@ -21,6 +21,8 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 
+import com.android.server.biometrics.BiometricsProto;
+
 public abstract class RevokeChallengeClient<T> extends HalClientMonitor<T> {
 
     public RevokeChallengeClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@@ -42,4 +44,9 @@
         startHalOperation();
         mCallback.onClientFinished(this, true /* success */);
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_REVOKE_CHALLENGE;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 54ab2e5..f37cf18 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -52,8 +52,8 @@
     }
 
     @Override
-    public byte[] dumpSensorServiceStateProto() throws RemoteException {
-        return mFaceService.dumpSensorServiceStateProto(mSensorId);
+    public byte[] dumpSensorServiceStateProto(boolean clearSchedulerBuffer) throws RemoteException {
+        return mFaceService.dumpSensorServiceStateProto(mSensorId, clearSchedulerBuffer);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index f055d55..1a63dde 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -147,13 +147,13 @@
         }
 
         @Override
-        public byte[] dumpSensorServiceStateProto(int sensorId) {
+        public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
 
             final ProtoOutputStream proto = new ProtoOutputStream();
             final ServiceProvider provider = getProviderForSensor(sensorId);
             if (provider != null) {
-                provider.dumpProtoState(sensorId, proto);
+                provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
             }
             proto.flush();
             return proto.getBytes();
@@ -405,7 +405,7 @@
                     final ProtoOutputStream proto = new ProtoOutputStream(fd);
                     for (ServiceProvider provider : mServiceProviders) {
                         for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
-                            provider.dumpProtoState(props.sensorId, proto);
+                            provider.dumpProtoState(props.sensorId, proto, false);
                         }
                     }
                     proto.flush();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 51b427d..32428ac1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -121,7 +121,8 @@
 
     void scheduleInternalCleanup(int sensorId, int userId);
 
-    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto);
+    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+            boolean clearSchedulerBuffer);
 
     void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
index 5fb194c..773647b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
@@ -23,6 +23,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.util.Map;
@@ -65,4 +66,9 @@
         mAuthenticatorIds.put(getTargetUserId(), authenticatorId);
         mCallback.onClientFinished(this, true /* success */);
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_GET_AUTHENTICATOR_ID;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 810489b1c..20318e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -571,9 +571,10 @@
     }
 
     @Override
-    public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
+    public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+            boolean clearSchedulerBuffer) {
         if (mSensors.contains(sensorId)) {
-            mSensors.get(sensorId).dumpProtoState(sensorId, proto);
+            mSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer);
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index f355158..71bac57 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -25,6 +25,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutCache;
@@ -81,4 +82,9 @@
         mLockoutResetDispatcher.notifyLockoutResetCallbacks(getSensorId());
         mCallback.onClientFinished(this, true /* success */);
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_RESET_LOCKOUT;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index b28713e..9b00ba6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -486,12 +486,13 @@
         mTestHalEnabled = enabled;
     }
 
-    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
+    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+            boolean clearSchedulerBuffer) {
         final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
 
         proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
         proto.write(SensorStateProto.MODALITY, SensorStateProto.FACE);
-        proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null);
+        proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer));
 
         for (UserInfo user : UserManager.get(mContext).getUsers()) {
             final int userId = user.getUserHandle().getIdentifier();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 5e7ddeb..7010d96 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -767,12 +767,13 @@
     }
 
     @Override
-    public void dumpProtoState(int sensorId, ProtoOutputStream proto) {
+    public void dumpProtoState(int sensorId, ProtoOutputStream proto,
+            boolean clearSchedulerBuffer) {
         final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
 
         proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
         proto.write(SensorStateProto.MODALITY, SensorStateProto.FACE);
-        proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null);
+        proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer));
 
         for (UserInfo user : UserManager.get(mContext).getUsers()) {
             final int userId = user.getUserHandle().getIdentifier();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index 442303b..722a3b8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -27,6 +27,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -88,4 +89,9 @@
     boolean getValue() {
         return mValue;
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_GET_FEATURE;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
index e0548e0..14a4648 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
@@ -23,6 +23,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.util.ArrayList;
@@ -71,4 +72,9 @@
             mCallback.onClientFinished(this, false /* success */);
         }
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_RESET_LOCKOUT;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
index 4356043..6290e00 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
@@ -25,6 +25,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -88,4 +89,9 @@
             mCallback.onClientFinished(this, false /* success */);
         }
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_SET_FEATURE;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
index 0e72f94..70e2033 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
@@ -24,6 +24,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.io.File;
@@ -91,4 +92,9 @@
             mCallback.onClientFinished(this, false /* success */);
         }
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_UPDATE_ACTIVE_USER;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 312a3ba..34a9099 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -53,8 +53,8 @@
     }
 
     @Override
-    public byte[] dumpSensorServiceStateProto() throws RemoteException {
-        return mFingerprintService.dumpSensorServiceStateProto(mSensorId);
+    public byte[] dumpSensorServiceStateProto(boolean clearSchedulerBuffer) throws RemoteException {
+        return mFingerprintService.dumpSensorServiceStateProto(mSensorId, clearSchedulerBuffer);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index d541eb3..0265cb9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -116,13 +116,13 @@
         }
 
         @Override
-        public byte[] dumpSensorServiceStateProto(int sensorId) {
+        public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
 
             final ProtoOutputStream proto = new ProtoOutputStream();
             final ServiceProvider provider = getProviderForSensor(sensorId);
             if (provider != null) {
-                provider.dumpProtoState(sensorId, proto);
+                provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
             }
             proto.flush();
             return proto.getBytes();
@@ -419,7 +419,7 @@
                     for (ServiceProvider provider : mServiceProviders) {
                         for (FingerprintSensorPropertiesInternal props
                                 : provider.getSensorProperties()) {
-                            provider.dumpProtoState(props.sensorId, proto);
+                            provider.dumpProtoState(props.sensorId, proto, false);
                         }
                     }
                     proto.flush();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 272e2b2..d3b5b5a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -128,7 +128,8 @@
 
     void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
 
-    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto);
+    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+            boolean clearSchedulerBuffer);
 
     void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 3b376fe..b4bb4f8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -27,6 +27,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
@@ -91,4 +92,9 @@
             mCallback.onClientFinished(this, false /* success */);
         }
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_DETECT_INTERACTION;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index 02d4ac3..ce1a318 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -23,6 +23,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.util.Map;
@@ -65,4 +66,9 @@
         mAuthenticatorIds.put(getTargetUserId(), authenticatorId);
         mCallback.onClientFinished(this, true /* success */);
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_GET_AUTHENTICATOR_ID;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 8a666f9..727184f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -627,9 +627,10 @@
     }
 
     @Override
-    public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
+    public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+            boolean clearSchedulerBuffer) {
         if (mSensors.contains(sensorId)) {
-            mSensors.get(sensorId).dumpProtoState(sensorId, proto);
+            mSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer);
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index cd84cdf..ddcfcad 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -25,6 +25,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutCache;
@@ -76,4 +77,9 @@
         mLockoutResetDispatcher.notifyLockoutResetCallbacks(getSensorId());
         mCallback.onClientFinished(this, true /* success */);
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_RESET_LOCKOUT;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 911f6b4..f0e7e1c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -481,12 +481,13 @@
         mTestHalEnabled = enabled;
     }
 
-    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
+    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+            boolean clearSchedulerBuffer) {
         final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
 
         proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
         proto.write(SensorStateProto.MODALITY, SensorStateProto.FINGERPRINT);
-        proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null);
+        proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer));
 
         for (UserInfo user : UserManager.get(mContext).getUsers()) {
             final int userId = user.getUserHandle().getIdentifier();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 6cc8687..acc575f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -714,12 +714,13 @@
     }
 
     @Override
-    public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
+    public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+            boolean clearSchedulerBuffer) {
         final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
 
         proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
         proto.write(SensorStateProto.MODALITY, SensorStateProto.FINGERPRINT);
-        proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null);
+        proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer));
 
         for (UserInfo user : UserManager.get(mContext).getUsers()) {
             final int userId = user.getUserHandle().getIdentifier();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 55995ea..6318139 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -28,6 +28,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -123,4 +124,9 @@
             Slog.e(TAG, "Remote exception when sending onDetected", e);
         }
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_DETECT_INTERACTION;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index f6ec4d9..11ffbb2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -26,6 +26,7 @@
 import android.os.SELinux;
 import android.util.Slog;
 
+import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.io.File;
@@ -121,4 +122,9 @@
             mCallback.onClientFinished(this, false /* success */);
         }
     }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_UPDATE_ACTIVE_USER;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 3e5b88c..8e84613 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -50,7 +50,7 @@
     }
 
     @Override
-    public byte[] dumpSensorServiceStateProto() throws RemoteException {
+    public byte[] dumpSensorServiceStateProto(boolean clearSchedulerBuffer) throws RemoteException {
         return null;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index cc4541b..832f918 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -34,11 +34,14 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoOutputStream;
 
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.nano.BiometricSchedulerProto;
+import com.android.server.biometrics.nano.BiometricsProto;
 import com.android.server.biometrics.sensors.BiometricScheduler.Operation;
 
 import org.junit.Before;
@@ -52,6 +55,7 @@
 
     private static final String TAG = "BiometricSchedulerTest";
     private static final int TEST_SENSOR_ID = 1;
+    private static final int LOG_NUM_RECENT_OPERATIONS = 2;
 
     private BiometricScheduler mScheduler;
     private IBinder mToken;
@@ -66,7 +70,7 @@
         MockitoAnnotations.initMocks(this);
         mToken = new Binder();
         mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */,
-                mBiometricService);
+                mBiometricService, LOG_NUM_RECENT_OPERATIONS);
     }
 
     @Test
@@ -186,6 +190,88 @@
         assertNull(mScheduler.mCurrentOperation);
     }
 
+    @Test
+    public void testProtoDump_singleCurrentOperation() throws Exception {
+        // Nothing so far
+        BiometricSchedulerProto bsp = getDump(true /* clearSchedulerBuffer */);
+        assertEquals(BiometricsProto.CM_NONE, bsp.currentOperation);
+        assertEquals(0, bsp.totalOperations);
+        assertEquals(0, bsp.recentOperations.length);
+
+        // Pretend the scheduler is busy enrolling, and check the proto dump again.
+        final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken,
+                () -> mock(Object.class), BiometricsProto.CM_ENROLL);
+        mScheduler.scheduleClientMonitor(client);
+        waitForIdle();
+        bsp = getDump(true /* clearSchedulerBuffer */);
+        assertEquals(BiometricsProto.CM_ENROLL, bsp.currentOperation);
+        // No operations have completed yet
+        assertEquals(0, bsp.totalOperations);
+        assertEquals(0, bsp.recentOperations.length);
+        // Finish this operation, so the next scheduled one can start
+        client.getCallback().onClientFinished(client, true);
+    }
+
+    @Test
+    public void testProtoDump_fifo() throws Exception {
+        // Add the first operation
+        final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken,
+                () -> mock(Object.class), BiometricsProto.CM_ENROLL);
+        mScheduler.scheduleClientMonitor(client);
+        waitForIdle();
+        BiometricSchedulerProto bsp = getDump(false /* clearSchedulerBuffer */);
+        assertEquals(BiometricsProto.CM_ENROLL, bsp.currentOperation);
+        // No operations have completed yet
+        assertEquals(0, bsp.totalOperations);
+        assertEquals(0, bsp.recentOperations.length);
+        // Finish this operation, so the next scheduled one can start
+        client.getCallback().onClientFinished(client, true);
+
+        // Add another operation
+        final TestClientMonitor2 client2 = new TestClientMonitor2(mContext, mToken,
+                () -> mock(Object.class), BiometricsProto.CM_REMOVE);
+        mScheduler.scheduleClientMonitor(client2);
+        waitForIdle();
+        bsp = getDump(false /* clearSchedulerBuffer */);
+        assertEquals(BiometricsProto.CM_REMOVE, bsp.currentOperation);
+        assertEquals(1, bsp.totalOperations); // Enroll finished
+        assertEquals(1, bsp.recentOperations.length);
+        assertEquals(BiometricsProto.CM_ENROLL, bsp.recentOperations[0]);
+        client2.getCallback().onClientFinished(client2, true);
+
+        // And another operation
+        final TestClientMonitor2 client3 = new TestClientMonitor2(mContext, mToken,
+                () -> mock(Object.class), BiometricsProto.CM_AUTHENTICATE);
+        mScheduler.scheduleClientMonitor(client3);
+        waitForIdle();
+        bsp = getDump(false /* clearSchedulerBuffer */);
+        assertEquals(BiometricsProto.CM_AUTHENTICATE, bsp.currentOperation);
+        assertEquals(2, bsp.totalOperations);
+        assertEquals(2, bsp.recentOperations.length);
+        assertEquals(BiometricsProto.CM_ENROLL, bsp.recentOperations[0]);
+        assertEquals(BiometricsProto.CM_REMOVE, bsp.recentOperations[1]);
+
+        // Finish the last operation, and check that the first operation is removed from the FIFO.
+        // The test initializes the scheduler with "LOG_NUM_RECENT_OPERATIONS = 2" :)
+        client3.getCallback().onClientFinished(client3, true);
+        waitForIdle();
+        bsp = getDump(true /* clearSchedulerBuffer */);
+        assertEquals(3, bsp.totalOperations);
+        assertEquals(2, bsp.recentOperations.length);
+        assertEquals(BiometricsProto.CM_REMOVE, bsp.recentOperations[0]);
+        assertEquals(BiometricsProto.CM_AUTHENTICATE, bsp.recentOperations[1]);
+        // Nothing is currently running anymore
+        assertEquals(BiometricsProto.CM_NONE, bsp.currentOperation);
+
+        // RecentOperations queue is cleared (by the previous dump)
+        bsp = getDump(true /* clearSchedulerBuffer */);
+        assertEquals(0, bsp.recentOperations.length);
+    }
+
+    private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
+        return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
+    }
+
     private static class BiometricPromptClientMonitor extends AuthenticationClient<Object> {
 
         public BiometricPromptClientMonitor(@NonNull Context context, @NonNull IBinder token,
@@ -207,6 +293,21 @@
         }
     }
 
+    private static class TestClientMonitor2 extends TestClientMonitor {
+        private final int mProtoEnum;
+
+        public TestClientMonitor2(@NonNull Context context, @NonNull IBinder token,
+                @NonNull LazyDaemon<Object> lazyDaemon, int protoEnum) {
+            super(context, token, lazyDaemon);
+            mProtoEnum = protoEnum;
+        }
+
+        @Override
+        public int getProtoEnum() {
+            return mProtoEnum;
+        }
+    }
+
     private static class TestClientMonitor extends HalClientMonitor<Object> {
         private boolean mUnableToStart;
         private boolean mStarted;
@@ -230,6 +331,13 @@
         }
 
         @Override
+        public int getProtoEnum() {
+            // Anything other than CM_NONE, which is used to represent "idle". Tests that need
+            // real proto enums should use TestClientMonitor2
+            return BiometricsProto.CM_UPDATE_ACTIVE_USER;
+        }
+
+        @Override
         public void start(@NonNull Callback callback) {
             super.start(callback);
             assertFalse(mStarted);