cameraservice: Add metrics for extension sessions

We want to log camera extension usage in cameraservice. This CL adds
the scaffolding for it. Specifically, it does the following:

  - Adds CameraExtensionSessionStats.aidl: This is used as the interface
    that CameraExtensionSession, CameraService, and CameraServiceProxy
    use to parcel extension metrics around.

  - Adds 'reportExtensionStats' to ICameraService: To keep the details
    of extensions away from cameraservice, CameraExtensionSession is
    responsible for collecting and reporting the stats. CameraService
    simply maps the reported stats to the current camera session and
    passes it to CameraServiceProxy when the session is closed.

    Some additional safeguards are added for this function because
    CameraExtensionSession can potentially report stats after the
    session has already been closed from cameraservice.

Bug: 261470491
Test: No functional change.
      `atest CtsCameraTestCases` passes.
      `statsd_testdrive 227` confirms that extension metrics are logged
          correctly.
Change-Id: I4a9897fc2b09cabb7618e94f8484757735c5e1f3
diff --git a/camera/Android.bp b/camera/Android.bp
index f27eb31..b3f70f4 100644
--- a/camera/Android.bp
+++ b/camera/Android.bp
@@ -142,14 +142,15 @@
 filegroup {
     name: "libcamera_client_aidl",
     srcs: [
+        "aidl/android/hardware/CameraExtensionSessionStats.aidl",
         "aidl/android/hardware/ICameraService.aidl",
         "aidl/android/hardware/ICameraServiceListener.aidl",
         "aidl/android/hardware/ICameraServiceProxy.aidl",
         "aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl",
         "aidl/android/hardware/camera2/ICameraDeviceUser.aidl",
-        "aidl/android/hardware/camera2/ICameraOfflineSession.aidl",
         "aidl/android/hardware/camera2/ICameraInjectionCallback.aidl",
         "aidl/android/hardware/camera2/ICameraInjectionSession.aidl",
+        "aidl/android/hardware/camera2/ICameraOfflineSession.aidl",
     ],
     path: "aidl",
 }
diff --git a/camera/CameraSessionStats.cpp b/camera/CameraSessionStats.cpp
index 3954db5..9e9793d 100644
--- a/camera/CameraSessionStats.cpp
+++ b/camera/CameraSessionStats.cpp
@@ -279,7 +279,8 @@
         mResultErrorCount(0),
         mDeviceError(false),
         mVideoStabilizationMode(-1),
-        mSessionIndex(0) {}
+        mSessionIndex(0),
+        mCameraExtensionSessionStats() {}
 
 CameraSessionStats::CameraSessionStats(const String16& cameraId,
         int facing, int newCameraState, const String16& clientName,
@@ -299,7 +300,8 @@
                 mResultErrorCount(0),
                 mDeviceError(0),
                 mVideoStabilizationMode(-1),
-                mSessionIndex(0) {}
+                mSessionIndex(0),
+                mCameraExtensionSessionStats() {}
 
 status_t CameraSessionStats::readFromParcel(const android::Parcel* parcel) {
     if (parcel == NULL) {
@@ -417,6 +419,12 @@
         return err;
     }
 
+    CameraExtensionSessionStats extStats{};
+    if ((err = extStats.readFromParcel(parcel)) != OK) {
+        ALOGE("%s: Failed to read extension session stats from parcel", __FUNCTION__);
+        return err;
+    }
+
     mCameraId = id;
     mFacing = facing;
     mNewCameraState = newCameraState;
@@ -435,6 +443,7 @@
     mUserTag = userTag;
     mVideoStabilizationMode = videoStabilizationMode;
     mSessionIndex = sessionIdx;
+    mCameraExtensionSessionStats = extStats;
 
     return OK;
 }
@@ -537,6 +546,11 @@
         return err;
     }
 
+    if ((err = mCameraExtensionSessionStats.writeToParcel(parcel)) != OK) {
+        ALOGE("%s: Failed to write extension sessions stats!", __FUNCTION__);
+        return err;
+    }
+
     return OK;
 }
 
diff --git a/camera/aidl/android/hardware/CameraExtensionSessionStats.aidl b/camera/aidl/android/hardware/CameraExtensionSessionStats.aidl
new file mode 100644
index 0000000..1c81831
--- /dev/null
+++ b/camera/aidl/android/hardware/CameraExtensionSessionStats.aidl
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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 android.hardware;
+
+/**
+ * Metrics specific to Extension Sessions (see CameraExtensionSession) for logging.
+ *
+ * Each Extension Session is mapped to one camera session internally, and will be sent to
+ * CameraServiceProxy with IDLE/CLOSE calls.
+ * @hide
+ */
+parcelable CameraExtensionSessionStats {
+    /**
+     * Value should match {@code CameraExtensionCharacteristics#EXTENSION_*}
+     */
+    @Backing(type="int")
+    enum Type {
+        EXTENSION_NONE = -1,
+        EXTENSION_AUTOMATIC = 0,
+        EXTENSION_FACE_RETOUCH = 1,
+        EXTENSION_BOKEH = 2,
+        EXTENSION_HDR = 3,
+        EXTENSION_NIGHT = 4
+    }
+
+    /**
+     * Key to uniquely identify the session this stat is associated with. The first call to
+     * 'ICameraService.reportExtensionSessionStats' should set this to an empty string.
+     * 'ICameraService.reportExtensionSessionStats' will return the key which should be used with
+     * the next calls.
+     */
+    String key;
+
+    /**
+     * Camera ID for which the stats is being reported.
+     */
+    String cameraId;
+
+    /**
+     * Package name of the client using the camera
+     */
+    String clientName;
+
+
+    /**
+     * Type of extension session requested by the app. Note that EXTENSION_AUTOMATIC is reported
+     * as such.
+     */
+    Type type = Type.EXTENSION_NONE;
+
+    /**
+     * true if advanced extensions are being used, false otherwise
+     */
+    boolean isAdvanced = false;
+}
\ No newline at end of file
diff --git a/camera/aidl/android/hardware/ICameraService.aidl b/camera/aidl/android/hardware/ICameraService.aidl
index 9f32595..f8e1631 100644
--- a/camera/aidl/android/hardware/ICameraService.aidl
+++ b/camera/aidl/android/hardware/ICameraService.aidl
@@ -30,6 +30,7 @@
 import android.hardware.ICameraServiceListener;
 import android.hardware.CameraInfo;
 import android.hardware.CameraStatus;
+import android.hardware.CameraExtensionSessionStats;
 
 /**
  * Binder interface for the native camera service running in mediaserver.
@@ -214,6 +215,26 @@
      */
     oneway void notifyDeviceStateChange(long newState);
 
+    /**
+     * Report Extension specific metrics to camera service for logging. This should only be called
+     * by CameraExtensionSession to log extension metrics. All calls after the first must set
+     * CameraExtensionSessionStats.key to the value returned by this function.
+     *
+     * Each subsequent call fully overwrites the existing CameraExtensionSessionStats for the
+     * current session, so the caller is responsible for keeping the stats complete.
+     *
+     * Due to cameraservice and cameraservice_proxy architecture, there is no guarantee that
+     * {@code stats} will be logged immediately (or at all). CameraService will log whatever
+     * extension stats it has at the time of camera session closing which may be before the app
+     * process receives a session/device closed callback; so CameraExtensionSession
+     * should send metrics to the cameraservice preriodically, and cameraservice must handle calls
+     * to this function from sessions that have not been logged yet and from sessions that have
+     * already been closed.
+     *
+     * @return the key that must be used to report updates to previously reported stats.
+     */
+    String reportExtensionSessionStats(in CameraExtensionSessionStats stats);
+
     // Bitfield constants for notifyDeviceStateChange
     // All bits >= 32 are for custom vendor states
     // Written as ints since AIDL does not support long constants.
diff --git a/camera/aidl/android/hardware/ICameraServiceProxy.aidl b/camera/aidl/android/hardware/ICameraServiceProxy.aidl
index be8a00f..4faa6b4 100644
--- a/camera/aidl/android/hardware/ICameraServiceProxy.aidl
+++ b/camera/aidl/android/hardware/ICameraServiceProxy.aidl
@@ -17,6 +17,7 @@
 package android.hardware;
 
 import android.hardware.CameraSessionStats;
+import android.hardware.CameraExtensionSessionStats;
 
 /**
  * Binder interface for the camera service proxy running in system_server.
diff --git a/camera/include/camera/CameraSessionStats.h b/camera/include/camera/CameraSessionStats.h
index 895449c..071bc73 100644
--- a/camera/include/camera/CameraSessionStats.h
+++ b/camera/include/camera/CameraSessionStats.h
@@ -20,6 +20,7 @@
 #include <binder/Parcelable.h>
 
 #include <camera/CameraMetadata.h>
+#include <android/hardware/CameraExtensionSessionStats.h>
 
 namespace android {
 namespace hardware {
@@ -160,6 +161,8 @@
     int mVideoStabilizationMode;
     int mSessionIndex;
 
+    CameraExtensionSessionStats mCameraExtensionSessionStats;
+
     // Constructors
     CameraSessionStats();
     CameraSessionStats(const String16& cameraId, int facing, int newCameraState,
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index eb824de..668a51a 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -3074,6 +3074,13 @@
     return binder::Status::ok();
 }
 
+Status CameraService::reportExtensionSessionStats(
+        const hardware::CameraExtensionSessionStats& stats, String16* sessionKey /*out*/) {
+    ALOGV("%s: reported %s", __FUNCTION__, stats.toString().c_str());
+    *sessionKey = mCameraServiceProxyWrapper->updateExtensionStats(stats);
+    return Status::ok();
+}
+
 void CameraService::removeByClient(const BasicClient* client) {
     Mutex::Autolock lock(mServiceLock);
     for (auto& i : mActiveClientManager.getAll()) {
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index d84cb00..3214d4c 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -218,6 +218,9 @@
             /*out*/
             sp<hardware::camera2::ICameraInjectionSession>* cameraInjectionSession);
 
+    virtual binder::Status reportExtensionSessionStats(
+            const hardware::CameraExtensionSessionStats& stats, String16* sessionKey /*out*/);
+
     // Extra permissions checks
     virtual status_t    onTransact(uint32_t code, const Parcel& data,
                                    Parcel* reply, uint32_t flags);
diff --git a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp
index af5f3ee..3aff2ac 100644
--- a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp
+++ b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp
@@ -26,8 +26,14 @@
 
 namespace android {
 
-using hardware::ICameraServiceProxy;
+using hardware::CameraExtensionSessionStats;
 using hardware::CameraSessionStats;
+using hardware::ICameraServiceProxy;
+
+namespace {
+// Sentinel value to be returned when extension session with a stale or invalid key is reported.
+const String16 POISON_EXT_STATS_KEY("poisoned_stats");
+} // anonymous namespace
 
 /**
  * CameraSessionStatsWrapper functions
@@ -97,10 +103,12 @@
     mSessionStats.mUserTag = String16(userTag.c_str());
     mSessionStats.mVideoStabilizationMode = videoStabilizationMode;
     mSessionStats.mStreamStats = streamStats;
+
     updateProxyDeviceState(proxyBinder);
 
     mSessionStats.mInternalReconfigure = 0;
     mSessionStats.mStreamStats.clear();
+    mSessionStats.mCameraExtensionSessionStats = {};
 }
 
 int64_t CameraServiceProxyWrapper::CameraSessionStatsWrapper::getLogId() {
@@ -108,6 +116,65 @@
     return mSessionStats.mLogId;
 }
 
+String16 CameraServiceProxyWrapper::CameraSessionStatsWrapper::updateExtensionSessionStats(
+        const hardware::CameraExtensionSessionStats& extStats) {
+    Mutex::Autolock l(mLock);
+    CameraExtensionSessionStats& currStats = mSessionStats.mCameraExtensionSessionStats;
+    if (currStats.key != extStats.key) {
+        // Mismatched keys. Extensions stats likely reported for a closed session
+        ALOGW("%s: mismatched extensions stats key: current='%s' reported='%s'. Dropping stats.",
+              __FUNCTION__, String8(currStats.key).c_str(), String8(extStats.key).c_str());
+        return POISON_EXT_STATS_KEY; // return poisoned key to so future calls are
+                                     // definitely dropped.
+    }
+
+    // Matching keys...
+    if (currStats.key.size()) {
+        // non-empty matching keys. overwrite.
+        ALOGV("%s: Overwriting extension session stats: %s", __FUNCTION__,
+              extStats.toString().c_str());
+        currStats = extStats;
+        return currStats.key;
+    }
+
+    // Matching empty keys...
+    if (mSessionStats.mClientName != extStats.clientName) {
+        ALOGW("%s: extension stats reported for unexpected package: current='%s' reported='%s'. "
+              "Dropping stats.", __FUNCTION__,
+              String8(mSessionStats.mClientName).c_str(),
+              String8(extStats.clientName).c_str());
+        return POISON_EXT_STATS_KEY;
+    }
+
+    // Matching empty keys for the current client...
+    if (mSessionStats.mNewCameraState == CameraSessionStats::CAMERA_STATE_OPEN ||
+        mSessionStats.mNewCameraState == CameraSessionStats::CAMERA_STATE_IDLE) {
+        // Camera is open, but not active. It is possible that the active callback hasn't
+        // occurred yet. Keep the stats, but don't associate it with any session.
+        ALOGV("%s: extension stat reported for an open, but not active camera. "
+              "Saving stats, but not generating key.", __FUNCTION__);
+        currStats = extStats;
+        return {}; // Subsequent calls will handle setting the correct key.
+    }
+
+    if (mSessionStats.mNewCameraState == CameraSessionStats::CAMERA_STATE_ACTIVE) {
+        // camera is active. First call for the session!
+        currStats = extStats;
+
+        // Generate a new key from logId and sessionIndex.
+        std::ostringstream key;
+        key << mSessionStats.mSessionIndex << '/' << mSessionStats.mLogId;
+        currStats.key = String16(key.str().c_str());
+        ALOGV("%s: New extension session stats: %s", __FUNCTION__, currStats.toString().c_str());
+        return currStats.key;
+    }
+
+    // Camera is closed. Probably a stale call.
+    ALOGW("%s: extension stats reported for closed camera id '%s'. Dropping stats.",
+          __FUNCTION__, String8(mSessionStats.mCameraId).c_str());
+    return {};
+}
+
 /**
  * CameraServiceProxyWrapper functions
  */
@@ -337,4 +404,21 @@
     return ret;
 }
 
+String16 CameraServiceProxyWrapper::updateExtensionStats(
+        const hardware::CameraExtensionSessionStats& extStats) {
+    std::shared_ptr<CameraSessionStatsWrapper> stats;
+    String8 cameraId = String8(extStats.cameraId);
+    {
+        Mutex::Autolock _l(mLock);
+        if (mSessionStatsMap.count(cameraId) == 0) {
+            ALOGE("%s CameraExtensionSessionStats reported for camera id that isn't open: %s",
+                  __FUNCTION__, cameraId.c_str());
+            return {};
+        }
+
+        stats = mSessionStatsMap[cameraId];
+        return stats->updateExtensionSessionStats(extStats);
+    }
+}
+
 }  // namespace android
diff --git a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h
index d47c738..e32580c 100644
--- a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h
+++ b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h
@@ -65,6 +65,8 @@
                 const std::string& userTag, int32_t videoStabilizationMode,
                 const std::vector<hardware::CameraStreamStats>& streamStats);
 
+        String16 updateExtensionSessionStats(const hardware::CameraExtensionSessionStats& extStats);
+
         // Returns the logId associated with this event.
         int64_t getLogId();
     };
@@ -127,6 +129,9 @@
     // frameworks/av/camera/include/camera/CameraSessionStats.h for more details about this
     // identifier. Returns a non-0 value on success.
     int64_t getCurrentLogIdForCamera(const String8& cameraId);
+
+    // Update the stored extension stats to the latest values
+    String16 updateExtensionStats(const hardware::CameraExtensionSessionStats& extStats);
 };
 
 } // android