MediaMetricsService: Parse Spatializer metrics items

Register an action for incoming Spatializer items.

Test: adb shell dumpsys media.metrics - check spatializer log
Bug: 233773341
Merged-In: I97c73fc1001f9ca669255820e48fb32c11489742
Change-Id: I97c73fc1001f9ca669255820e48fb32c11489742
(cherry picked from commit b942702a91d61827280a2f6469054e311b9b7eec)
diff --git a/media/libmediametrics/include/MediaMetricsConstants.h b/media/libmediametrics/include/MediaMetricsConstants.h
index 1c30510..49abdf0 100644
--- a/media/libmediametrics/include/MediaMetricsConstants.h
+++ b/media/libmediametrics/include/MediaMetricsConstants.h
@@ -57,6 +57,10 @@
 // The Audio Spatializer key appends the spatializerId (currently 0)
 #define AMEDIAMETRICS_KEY_PREFIX_AUDIO_SPATIALIZER AMEDIAMETRICS_KEY_PREFIX_AUDIO "spatializer."
 
+// The Audio Spatializer device key appends the device type.
+#define AMEDIAMETRICS_KEY_PREFIX_AUDIO_SPATIALIZER_DEVICE \
+        AMEDIAMETRICS_KEY_PREFIX_AUDIO_SPATIALIZER "device."
+
 // The AudioStream key appends the "streamId" to the prefix.
 #define AMEDIAMETRICS_KEY_PREFIX_AUDIO_STREAM  AMEDIAMETRICS_KEY_PREFIX_AUDIO "stream."
 
@@ -132,6 +136,7 @@
 
 #define AMEDIAMETRICS_PROP_DIRECTION      "direction"      // string AAudio input or output
 #define AMEDIAMETRICS_PROP_DURATIONNS     "durationNs"     // int64 duration time span
+#define AMEDIAMETRICS_PROP_ENABLED        "enabled"        // string true/false.
 #define AMEDIAMETRICS_PROP_ENCODING       "encoding"       // string value of format
 
 #define AMEDIAMETRICS_PROP_EVENT          "event#"         // string value (often func name)
@@ -141,6 +146,8 @@
 #define AMEDIAMETRICS_PROP_FLAGS          "flags"
 
 #define AMEDIAMETRICS_PROP_FRAMECOUNT     "frameCount"     // int32
+#define AMEDIAMETRICS_PROP_HASHEADTRACKER  "hasHeadTracker" // string true/false
+#define AMEDIAMETRICS_PROP_HEADTRACKERENABLED "headTrackerEnabled" // string true/false
 #define AMEDIAMETRICS_PROP_HEADTRACKINGMODES "headTrackingModes" // string |, like modes.
 #define AMEDIAMETRICS_PROP_INPUTDEVICES   "inputDevices"   // string value
 #define AMEDIAMETRICS_PROP_INTERNALTRACKID "internalTrackId" // int32
diff --git a/services/mediametrics/AudioAnalytics.cpp b/services/mediametrics/AudioAnalytics.cpp
index 99e3691..765a076 100644
--- a/services/mediametrics/AudioAnalytics.cpp
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -459,6 +459,15 @@
             [this](const std::shared_ptr<const android::mediametrics::Item> &item){
                 mAudioPowerUsage.checkCreatePatch(item);
             }));
+
+    // Handle Spatializer - these keys are prefixed by "audio.spatializer."
+    mActions.addAction(
+        AMEDIAMETRICS_KEY_PREFIX_AUDIO_SPATIALIZER "*." AMEDIAMETRICS_PROP_EVENT,
+        std::monostate{}, /* match any event */
+        std::make_shared<AnalyticsActions::Function>(
+            [this](const std::shared_ptr<const android::mediametrics::Item> &item){
+                mSpatializer.onEvent(item);
+            }));
 }
 
 AudioAnalytics::~AudioAnalytics()
@@ -1525,5 +1534,109 @@
     return { s, n };
 }
 
+void AudioAnalytics::Spatializer::onEvent(
+        const std::shared_ptr<const android::mediametrics::Item> &item)
+{
+    const auto key = item->getKey();
+
+    if (!startsWith(key, AMEDIAMETRICS_KEY_PREFIX_AUDIO_SPATIALIZER)) return;
+
+    const std::string suffix =
+            key.substr(std::size(AMEDIAMETRICS_KEY_PREFIX_AUDIO_SPATIALIZER) - 1);
+
+    std::string eventStr; // optional - find the actual event string.
+    (void)item->get(AMEDIAMETRICS_PROP_EVENT, &eventStr);
+
+    const size_t delim = suffix.find('.'); // note could use split.
+    if (delim == suffix.npos) {
+        // on create with suffix == "0" for the first spatializer effect.
+
+        std::string headTrackingModes;
+        (void)item->get(AMEDIAMETRICS_PROP_HEADTRACKINGMODES, &headTrackingModes);
+
+        std::string levels;
+        (void)item->get(AMEDIAMETRICS_PROP_LEVELS, &levels);
+
+        std::string modes;
+        (void)item->get(AMEDIAMETRICS_PROP_MODES, &modes);
+
+        int32_t channelMask = 0;
+        (void)item->get(AMEDIAMETRICS_PROP_CHANNELMASK, &channelMask);
+
+        LOG(LOG_LEVEL) << "key:" << key
+                << " headTrackingModes:" << headTrackingModes
+                << " levels:" << levels
+                << " modes:" << modes
+                << " channelMask:" << channelMask
+                ;
+
+        const std::vector<int32_t> headTrackingModesVector =
+                types::vectorFromMap(headTrackingModes, types::getHeadTrackingModeMap());
+        const std::vector<int32_t> levelsVector =
+                types::vectorFromMap(levels, types::getSpatializerLevelMap());
+        const std::vector<int32_t> modesVector =
+                types::vectorFromMap(modes, types::getSpatializerModeMap());
+
+        // TODO: send to statsd
+
+        std::lock_guard lg(mLock);
+        mSimpleLog.log("%s suffix: %s item: %s",
+                __func__, suffix.c_str(), item->toString().c_str());
+    } else {
+        std::string subtype = suffix.substr(0, delim);
+        if (subtype != "device") return; // not a device.
+
+        std::string deviceType = suffix.substr(std::size("device.") - 1);
+
+        std::string enabled;
+        (void)item->get(AMEDIAMETRICS_PROP_ENABLED, &enabled);
+        std::string hasHeadTracker;
+        (void)item->get(AMEDIAMETRICS_PROP_HASHEADTRACKER, &hasHeadTracker);
+        std::string headTrackerEnabled;
+        (void)item->get(AMEDIAMETRICS_PROP_HEADTRACKERENABLED, &headTrackerEnabled);
+
+        std::lock_guard lg(mLock);
+
+        // Validate from our cached state
+        DeviceState& deviceState = mDeviceStateMap[deviceType];
+
+        if (!enabled.empty()) {
+            if (enabled != deviceState.enabled) {
+                deviceState.enabled = enabled;
+                const bool enabledStatsd = enabled == "true";
+                // TODO: send to statsd
+                (void)mAudioAnalytics;
+                (void)enabledStatsd;
+            }
+        }
+        if (!hasHeadTracker.empty()) {
+            if (hasHeadTracker != deviceState.hasHeadTracker) {
+                deviceState.hasHeadTracker = hasHeadTracker;
+                const bool supportedStatsd = hasHeadTracker == "true";
+                // TODO: send to statsd
+                (void)supportedStatsd;
+            }
+        }
+        if (!headTrackerEnabled.empty()) {
+            if (headTrackerEnabled != deviceState.headTrackerEnabled) {
+                deviceState.headTrackerEnabled = headTrackerEnabled;
+                const bool enabledStatsd = headTrackerEnabled == "true";
+                // TODO: send to statsd
+                (void)enabledStatsd;
+            }
+        }
+        mSimpleLog.log("%s deviceType: %s item: %s",
+                __func__, deviceType.c_str(), item->toString().c_str());
+    }
+}
+
+std::pair<std::string, int32_t> AudioAnalytics::Spatializer::dump(
+        int32_t lines, const char *prefix) const
+{
+    std::lock_guard lg(mLock);
+    std::string s = mSimpleLog.dumpToString(prefix == nullptr ? "" : prefix, lines);
+    size_t n = std::count(s.begin(), s.end(), '\n');
+    return { s, n };
+}
 
 } // namespace android::mediametrics
diff --git a/services/mediametrics/AudioTypes.cpp b/services/mediametrics/AudioTypes.cpp
index 6e24a58..f2515bb 100644
--- a/services/mediametrics/AudioTypes.cpp
+++ b/services/mediametrics/AudioTypes.cpp
@@ -135,6 +135,50 @@
     return map;
 }
 
+// A map for the Java AudioDeviceInfo types to internal (native) output devices.
+const std::unordered_map<std::string, int32_t>& getAudioDeviceOutCompactMap() {
+    // DO NOT MODIFY VALUES (OK to add new ones).
+    static std::unordered_map<std::string, int32_t> map{
+        // should "unknown" go to AUDIO_DEVICE_NONE?
+        {"earpiece", AUDIO_DEVICE_OUT_EARPIECE},
+        {"speaker", AUDIO_DEVICE_OUT_SPEAKER},
+        {"headset", AUDIO_DEVICE_OUT_WIRED_HEADSET},
+        {"headphone", AUDIO_DEVICE_OUT_WIRED_HEADPHONE},
+        {"bt_sco", AUDIO_DEVICE_OUT_BLUETOOTH_SCO},
+        {"bt_sco_hs", AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET},
+        {"bt_sco_carkit", AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT},
+        {"bt_a2dp", AUDIO_DEVICE_OUT_BLUETOOTH_A2DP},
+        {"bt_a2dp_hp", AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES},
+        {"bt_a2dp_spk", AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER},
+        {"aux_digital", AUDIO_DEVICE_OUT_AUX_DIGITAL},
+        {"hdmi", AUDIO_DEVICE_OUT_HDMI},
+        {"analog_dock", AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET},
+        {"digital_dock", AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET},
+        {"usb_accessory", AUDIO_DEVICE_OUT_USB_ACCESSORY},
+        {"usb_device", AUDIO_DEVICE_OUT_USB_DEVICE},
+        {"remote_submix", AUDIO_DEVICE_OUT_REMOTE_SUBMIX},
+        {"telephony_tx", AUDIO_DEVICE_OUT_TELEPHONY_TX},
+        {"line", AUDIO_DEVICE_OUT_LINE},
+        {"hdmi_arc", AUDIO_DEVICE_OUT_HDMI_ARC},
+        {"hdmi_earc", AUDIO_DEVICE_OUT_HDMI_EARC},
+        {"spdif", AUDIO_DEVICE_OUT_SPDIF},
+        {"fm_transmitter", AUDIO_DEVICE_OUT_FM},
+        {"aux_line", AUDIO_DEVICE_OUT_AUX_LINE},
+        {"speaker_safe", AUDIO_DEVICE_OUT_SPEAKER_SAFE},
+        {"ip", AUDIO_DEVICE_OUT_IP},
+        {"bus", AUDIO_DEVICE_OUT_BUS},
+        {"proxy", AUDIO_DEVICE_OUT_PROXY},
+        {"usb_headset", AUDIO_DEVICE_OUT_USB_HEADSET},
+        {"hearing_aid_out", AUDIO_DEVICE_OUT_HEARING_AID},
+        {"echo_canceller", AUDIO_DEVICE_OUT_ECHO_CANCELLER},
+        // default does not exist
+        {"ble_headset", AUDIO_DEVICE_OUT_BLE_HEADSET},
+        {"ble_speaker", AUDIO_DEVICE_OUT_BLE_SPEAKER},
+        {"ble_broadcast", AUDIO_DEVICE_OUT_BLE_BROADCAST},
+    };
+    return map;
+}
+
 const std::unordered_map<std::string, int32_t>& getAudioThreadTypeMap() {
     // DO NOT MODIFY VALUES (OK to add new ones).
     // This may be found in frameworks/av/services/audioflinger/Threads.h
@@ -197,6 +241,41 @@
     return map;
 }
 
+const std::unordered_map<std::string, int32_t>& getHeadTrackingModeMap() {
+    // DO NOT MODIFY VALUES(OK to add new ones).
+    // frameworks/base/media/java/android/media/Spatializer.java
+    // frameworks/av/media/libaudioclient/aidl/android/media/SpatializerHeadTrackingMode.aidl
+    static std::unordered_map<std::string, int32_t> map {
+        {"OTHER", 0},
+        {"DISABLED", -1},
+        {"RELATIVE_WORLD", 1},
+        {"RELATIVE_SCREEN", 2},
+    };
+    return map;
+}
+
+const std::unordered_map<std::string, int32_t>& getSpatializerLevelMap() {
+    // DO NOT MODIFY VALUES(OK to add new ones).
+    // frameworks/base/media/java/android/media/Spatializer.java
+    // frameworks/av/media/libaudioclient/aidl/android/media/SpatializerHeadTrackingMode.aidl
+    static std::unordered_map<std::string, int32_t> map {
+        {"NONE", 0},
+        {"SPATIALIZER_MULTICHANNEL", 1},
+        {"SPATIALIZER_MCHAN_BED_PLUS_OBJECTS", 2},
+    };
+    return map;
+}
+
+const std::unordered_map<std::string, int32_t>& getSpatializerModeMap() {
+    // DO NOT MODIFY VALUES(OK to add new ones).
+    // frameworks/av/media/libaudioclient/aidl/android/media/SpatializationMode.aidl
+    static std::unordered_map<std::string, int32_t> map {
+        {"SPATIALIZER_BINAURAL", 0},
+        {"SPATIALIZER_TRANSAURAL", 1},
+    };
+    return map;
+}
+
 const std::unordered_map<std::string, int32_t>& getStatusMap() {
     // DO NOT MODIFY VALUES(OK to add new ones).
     static std::unordered_map<std::string, int32_t> map {
@@ -286,6 +365,22 @@
     return value;
 }
 
+std::vector<int32_t> vectorFromMap(
+        const std::string &str, const std::unordered_map<std::string, int32_t>& map)
+{
+    std::vector<int32_t> v;
+
+    if (str.empty()) return v;
+
+    const auto result = stringutils::split(str, "|");
+    for (const auto &s : result) {
+        auto it = map.find(s);
+        if (it == map.end()) continue;
+        v.push_back(it->second);
+    }
+    return v;
+}
+
 template <>
 int32_t lookup<CONTENT_TYPE>(const std::string &contentType)
 {
@@ -463,6 +558,39 @@
 }
 
 template <>
+int32_t lookup<HEAD_TRACKING_MODE>(const std::string& headTrackingMode)
+{
+    auto& map = getHeadTrackingModeMap();
+    auto it = map.find(headTrackingMode);
+    if (it == map.end()) {
+        return 0;
+    }
+    return it->second;
+}
+
+template <>
+int32_t lookup<SPATIALIZER_LEVEL>(const std::string& spatializerLevel)
+{
+    auto& map = getSpatializerLevelMap();
+    auto it = map.find(spatializerLevel);
+    if (it == map.end()) {
+        return 0;
+    }
+    return it->second;
+}
+
+template <>
+int32_t lookup<SPATIALIZER_MODE>(const std::string& spatializerMode)
+{
+    auto& map = getSpatializerModeMap();
+    auto it = map.find(spatializerMode);
+    if (it == map.end()) {
+        return 0;
+    }
+    return it->second;
+}
+
+template <>
 int32_t lookup<STATUS>(const std::string &status)
 {
     auto& map = getStatusMap();
diff --git a/services/mediametrics/MediaMetricsService.cpp b/services/mediametrics/MediaMetricsService.cpp
index 9e2f896..ceb3e6a 100644
--- a/services/mediametrics/MediaMetricsService.cpp
+++ b/services/mediametrics/MediaMetricsService.cpp
@@ -338,6 +338,15 @@
                 result << "-- some lines may be truncated --\n";
             }
 
+            const int32_t spatializerLinesToDump = all ? INT32_MAX : 15;
+            result << "\nSpatializer Message Log:";
+            const auto [ spatializerDumpString, spatializerLines ] =
+                    mAudioAnalytics.dumpSpatializer(spatializerLinesToDump);
+            result << "\n" << spatializerDumpString;
+            if (spatializerLines == spatializerLinesToDump) {
+                result << "-- some lines may be truncated --\n";
+            }
+
             result << "\nLogSessionId:\n"
                    << mediametrics::ValidateId::get()->dump();
 
diff --git a/services/mediametrics/include/mediametricsservice/AudioAnalytics.h b/services/mediametrics/include/mediametricsservice/AudioAnalytics.h
index 5ee8c30..d51dc59 100644
--- a/services/mediametrics/include/mediametricsservice/AudioAnalytics.h
+++ b/services/mediametrics/include/mediametricsservice/AudioAnalytics.h
@@ -92,6 +92,15 @@
         return mHealth.dump(lines);
     }
 
+    /**
+     * Returns a pair consisting of the dump string and the number of lines in the string.
+     *
+     * Spatializer dump.
+     */
+    std::pair<std::string, int32_t> dumpSpatializer(int32_t lines = INT32_MAX) const {
+        return mSpatializer.dump(lines);
+    }
+
     void clear() {
         // underlying state is locked.
         mPreviousAnalyticsState->clear();
@@ -317,6 +326,35 @@
         SimpleLog mSimpleLog GUARDED_BY(mLock) {64};
     } mHealth{*this};
 
+    // Spatializer is a nested class that tracks related messages.
+    class Spatializer {
+    public:
+        explicit Spatializer(AudioAnalytics &audioAnalytics)
+            : mAudioAnalytics(audioAnalytics) {}
+
+        // an item that starts with "audio.spatializer"
+        void onEvent(const std::shared_ptr<const android::mediametrics::Item> &item);
+
+        std::pair<std::string, int32_t> dump(
+                int32_t lines = INT32_MAX, const char *prefix = nullptr) const;
+
+    private:
+
+        // Current device state as strings:
+        // "" means unknown, "true" or "false".
+        struct DeviceState {
+            std::string enabled;
+            std::string hasHeadTracker;
+            std::string headTrackerEnabled;
+        };
+
+        AudioAnalytics& mAudioAnalytics;
+
+        mutable std::mutex mLock;
+        std::map<std::string, DeviceState> mDeviceStateMap GUARDED_BY(mLock);
+        SimpleLog mSimpleLog GUARDED_BY(mLock) {64};
+    } mSpatializer{*this};
+
     AudioPowerUsage mAudioPowerUsage;
 };
 
diff --git a/services/mediametrics/include/mediametricsservice/AudioTypes.h b/services/mediametrics/include/mediametricsservice/AudioTypes.h
index 5dbff9b..0184baf 100644
--- a/services/mediametrics/include/mediametricsservice/AudioTypes.h
+++ b/services/mediametrics/include/mediametricsservice/AudioTypes.h
@@ -29,6 +29,12 @@
 const std::unordered_map<std::string, int64_t>& getAudioDeviceOutMap();
 const std::unordered_map<std::string, int32_t>& getAudioThreadTypeMap();
 const std::unordered_map<std::string, int32_t>& getAudioTrackTraitsMap();
+const std::unordered_map<std::string, int32_t>& getHeadTrackingModeMap();
+const std::unordered_map<std::string, int32_t>& getSpatializerLevelMap();
+const std::unordered_map<std::string, int32_t>& getSpatializerModeMap();
+
+std::vector<int32_t> vectorFromMap(
+        const std::string &str, const std::unordered_map<std::string, int32_t>& map);
 
 // Enumeration for the device connection results.
 enum DeviceConnectionResult : int32_t {
@@ -50,11 +56,14 @@
     CALLER_NAME,
     CONTENT_TYPE,
     ENCODING,
+    HEAD_TRACKING_MODE,
     INPUT_DEVICE,  // int64_t
     INPUT_FLAG,
     OUTPUT_DEVICE, // int64_t
     OUTPUT_FLAG,
     SOURCE_TYPE,
+    SPATIALIZER_LEVEL,
+    SPATIALIZER_MODE,
     STATUS,
     STREAM_TYPE,
     THREAD_TYPE,
diff --git a/services/mediametrics/include/mediametricsservice/MediaMetricsService.h b/services/mediametrics/include/mediametricsservice/MediaMetricsService.h
index 8d0b1cf..3ec5ac7 100644
--- a/services/mediametrics/include/mediametricsservice/MediaMetricsService.h
+++ b/services/mediametrics/include/mediametricsservice/MediaMetricsService.h
@@ -125,7 +125,7 @@
     std::atomic<int64_t> mItemsSubmitted{}; // accessed outside of lock.
 
     // mStatsdLog is locked internally (thread-safe) and shows the last atoms logged
-    static constexpr size_t STATSD_LOG_LINES_MAX = 30; // recent log lines to keep
+    static constexpr size_t STATSD_LOG_LINES_MAX = 48; // recent log lines to keep
     static constexpr size_t STATSD_LOG_LINES_DUMP = 4; // normal amount of lines to dump
     const std::shared_ptr<mediametrics::StatsdLog> mStatsdLog{
             std::make_shared<mediametrics::StatsdLog>(STATSD_LOG_LINES_MAX)};
diff --git a/services/mediametrics/include/mediametricsservice/StringUtils.h b/services/mediametrics/include/mediametricsservice/StringUtils.h
index a56f5b8..a91d37b 100644
--- a/services/mediametrics/include/mediametricsservice/StringUtils.h
+++ b/services/mediametrics/include/mediametricsservice/StringUtils.h
@@ -23,6 +23,19 @@
 
 namespace android::mediametrics::stringutils {
 
+// Define a way of printing a vector - this
+// is used for proto repeated arguments.
+template <typename T>
+inline std::ostream & operator<< (std::ostream& s,
+                           std::vector<T> const& v) {
+    s << "{ ";
+    for (const auto& e : v) {
+        s << e << " ";
+    }
+    s << "}";
+    return s;
+}
+
 /**
  * fieldPrint is a helper method that logs to a stringstream a sequence of
  * field names (in a fixed size array) together with a variable number of arg parameters.