Camera: Add metrics logging for FPS range

- Get new most common FPS values from camera service
- Log them as appropriate
- Add shell command handling to CameraServiceProxy
  - Implement dump_events command to allow metrics events to be
    more easily debugged:
      > adb shell cmd media.camera.proxy dump_events
      Camera usage events dumped: 3
      >

Bug: 324485285
Test: Camera CTS, verifying logging of events occurs as expected
Change-Id: Id74730120c655cd53c18fde71d87c2910b6fd797
diff --git a/core/java/android/hardware/CameraSessionStats.java b/core/java/android/hardware/CameraSessionStats.java
index 32938ff..e5c12e3 100644
--- a/core/java/android/hardware/CameraSessionStats.java
+++ b/core/java/android/hardware/CameraSessionStats.java
@@ -16,9 +16,11 @@
 package android.hardware;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Range;
 
 import java.util.ArrayList;
 import java.util.List;
+
 /**
  * The camera action state used for passing camera usage information from
  * camera service to camera service proxy .
@@ -66,6 +68,7 @@
     private int mVideoStabilizationMode;
     private boolean mUsedUltraWide;
     private boolean mUsedZoomOverride;
+    private Range<Integer> mMostRequestedFpsRange;
     private int mSessionIndex;
     private CameraExtensionSessionStats mCameraExtensionSessionStats;
 
@@ -86,6 +89,7 @@
         mVideoStabilizationMode = -1;
         mUsedUltraWide = false;
         mUsedZoomOverride = false;
+        mMostRequestedFpsRange = new Range<Integer>(0, 0);
         mSessionIndex = 0;
         mCameraExtensionSessionStats = new CameraExtensionSessionStats();
     }
@@ -109,6 +113,7 @@
         mVideoStabilizationMode = -1;
         mUsedUltraWide = false;
         mUsedZoomOverride = false;
+        mMostRequestedFpsRange = new Range<Integer>(0, 0);
         mSessionIndex = sessionIdx;
         mCameraExtensionSessionStats = new CameraExtensionSessionStats();
     }
@@ -158,6 +163,8 @@
         dest.writeBoolean(mUsedZoomOverride);
         dest.writeInt(mSessionIndex);
         mCameraExtensionSessionStats.writeToParcel(dest, 0);
+        dest.writeInt(mMostRequestedFpsRange.getLower());
+        dest.writeInt(mMostRequestedFpsRange.getUpper());
     }
 
     public void readFromParcel(Parcel in) {
@@ -188,6 +195,9 @@
 
         mSessionIndex = in.readInt();
         mCameraExtensionSessionStats = CameraExtensionSessionStats.CREATOR.createFromParcel(in);
+        int minFps = in.readInt();
+        int maxFps = in.readInt();
+        mMostRequestedFpsRange = new Range<Integer>(minFps, maxFps);
     }
 
     public String getCameraId() {
@@ -273,4 +283,8 @@
     public CameraExtensionSessionStats getExtensionSessionStats() {
         return mCameraExtensionSessionStats;
     }
+
+    public Range<Integer> getMostRequestedFpsRange() {
+        return mMostRequestedFpsRange;
+    }
 }
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 05e681e..645a366 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -61,6 +61,9 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -69,6 +72,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Range;
 import android.util.Slog;
 import android.view.Display;
 import android.view.IDisplayWindowListener;
@@ -85,6 +89,8 @@
 import com.android.server.SystemService;
 import com.android.server.wm.WindowManagerInternal;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -243,6 +249,7 @@
         public int mVideoStabilizationMode;
         public boolean mUsedUltraWide;
         public boolean mUsedZoomOverride;
+        public Range<Integer> mMostRequestedFpsRange;
         public final long mLogId;
         public final int mSessionIndex;
 
@@ -265,13 +272,15 @@
             mDeviceError = deviceError;
             mLogId = logId;
             mSessionIndex = sessionIdx;
+            mMostRequestedFpsRange = new Range<Integer>(0, 0);
         }
 
         public void markCompleted(int internalReconfigure, long requestCount,
                 long resultErrorCount, boolean deviceError,
                 List<CameraStreamStats>  streamStats, String userTag,
                 int videoStabilizationMode, boolean usedUltraWide,
-                boolean usedZoomOverride, CameraExtensionSessionStats extStats) {
+                boolean usedZoomOverride, Range<Integer> mostRequestedFpsRange,
+                CameraExtensionSessionStats extStats) {
             if (mCompleted) {
                 return;
             }
@@ -287,6 +296,7 @@
             mUsedUltraWide = usedUltraWide;
             mUsedZoomOverride = usedZoomOverride;
             mExtSessionStats = extStats;
+            mMostRequestedFpsRange = mostRequestedFpsRange;
             if (CameraServiceProxy.DEBUG) {
                 Slog.v(TAG, "A camera facing " + cameraFacingToString(mCameraFacing) +
                         " was in use by " + mClientName + " for " +
@@ -637,6 +647,60 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                String[] args, ShellCallback callback, ResultReceiver resultReceiver)
+                throws RemoteException {
+            new CSPShellCmd(CameraServiceProxy.this)
+                .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+
+        private static class CSPShellCmd extends ShellCommand {
+            private static final String TAG = "CSPShellCmd";
+            private static final String USAGE = """
+                    usage: cmd media.camera.proxy SUBCMD [args]
+
+                    SUBCMDs:
+                        dump_events: Write out all collected camera usage events to statsd.
+                            Does not print to terminal.
+                        help: You're reading it.
+                    """;
+
+            private final CameraServiceProxy mCameraServiceProxy;
+
+            CSPShellCmd(CameraServiceProxy proxy) {
+                mCameraServiceProxy = proxy;
+            }
+
+            @Override
+            public int onCommand(String cmd) {
+                if (cmd == null) {
+                    return handleDefaultCommands(cmd);
+                }
+                final PrintWriter pw = getOutPrintWriter();
+                try {
+                    switch (cmd.replace('-', '_')) {
+                        case "dump_events":
+                            int eventCount = mCameraServiceProxy.getUsageEventCount();
+                            mCameraServiceProxy.dumpUsageEvents();
+                            pw.println("Camera usage events dumped: " + eventCount);
+                            break;
+                        default:
+                            return handleDefaultCommands(cmd);
+                    }
+                } catch (Exception e) {
+                    Slog.e(mCameraServiceProxy.TAG, "Error running shell command", e);
+                    return 1;
+                }
+                return 0;
+            }
+
+            @Override
+            public void onHelp() {
+                getOutPrintWriter().println(USAGE);
+            }
+        }
     };
 
     private final FoldStateListener mFoldStateListener;
@@ -882,6 +946,9 @@
                         ? ", zoomOverrideUsage " + e.mUsedZoomOverride
                         : "";
 
+                String mostRequestedFpsRangeDebug = Flags.analytics24q3()
+                        ? ", mostRequestedFpsRange " + e.mMostRequestedFpsRange
+                        : "";
                 Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction
                         + " clientName " + e.mClientName
                         + ", duration " + e.getDuration()
@@ -900,6 +967,7 @@
                         + ", videoStabilizationMode " + e.mVideoStabilizationMode
                         + ultrawideDebug
                         + zoomOverrideDebug
+                        + mostRequestedFpsRangeDebug
                         + ", logId " + e.mLogId
                         + ", sessionIndex " + e.mSessionIndex
                         + ", mExtSessionStats {type " + extensionType
@@ -966,7 +1034,17 @@
                     e.mUserTag, e.mVideoStabilizationMode,
                     e.mLogId, e.mSessionIndex,
                     extensionType, extensionIsAdvanced, e.mUsedUltraWide,
-                    e.mUsedZoomOverride);
+                    e.mUsedZoomOverride,
+                    e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper());
+        }
+    }
+
+    /**
+     * Get camera usage event count
+     */
+    int getUsageEventCount() {
+        synchronized (mLock) {
+            return mCameraUsageHistory.size();
         }
     }
 
@@ -1173,6 +1251,10 @@
         long logId = cameraState.getLogId();
         int sessionIdx = cameraState.getSessionIndex();
         CameraExtensionSessionStats extSessionStats = cameraState.getExtensionSessionStats();
+        Range<Integer> mostRequestedFpsRange = Flags.analytics24q3()
+                ? cameraState.getMostRequestedFpsRange()
+                : new Range<Integer>(0, 0);
+
         synchronized(mLock) {
             // Update active camera list and notify NFC if necessary
             boolean wasEmpty = mActiveCameraUsage.isEmpty();
@@ -1228,7 +1310,8 @@
                         oldEvent.markCompleted(/*internalReconfigure*/0, /*requestCount*/0,
                                 /*resultErrorCount*/0, /*deviceError*/false, streamStats,
                                 /*userTag*/"", /*videoStabilizationMode*/-1, /*usedUltraWide*/false,
-                                /*usedZoomOverride*/false, new CameraExtensionSessionStats());
+                                /*usedZoomOverride*/false, new Range<Integer>(0, 0),
+                                new CameraExtensionSessionStats());
                         mCameraUsageHistory.add(oldEvent);
                     }
                     break;
@@ -1240,7 +1323,7 @@
                         doneEvent.markCompleted(internalReconfigureCount, requestCount,
                                 resultErrorCount, deviceError, streamStats, userTag,
                                 videoStabilizationMode, usedUltraWide, usedZoomOverride,
-                                extSessionStats);
+                                mostRequestedFpsRange, extSessionStats);
                         mCameraUsageHistory.add(doneEvent);
                         // Do not double count device error
                         deviceError = false;