MediaMetrics: Implement AudioTrack status logging

Convert error field to status field to be more generic.
Incorporate the HeatMap to track general item status.

Test: atest AudioTrackTest#testBuilderError
Test: adb shell dumpsys media.metrics
Bug: 199763036
Merged-In: I53f3064e614d7e49bed3bf63cc5e6faf19684d2f
Change-Id: I53f3064e614d7e49bed3bf63cc5e6faf19684d2f
(cherry picked from commit 73dc2f9f850e226615d4b7b832dbcd4ce49dc55d)
diff --git a/media/libaudioclient/AudioRecord.cpp b/media/libaudioclient/AudioRecord.cpp
index a1d3bdb..da21771 100644
--- a/media/libaudioclient/AudioRecord.cpp
+++ b/media/libaudioclient/AudioRecord.cpp
@@ -972,6 +972,7 @@
         .set(AMEDIAMETRICS_PROP_SAMPLERATE, (int32_t)mSampleRate)
         // the following are NOT immutable
         .set(AMEDIAMETRICS_PROP_STATE, stateToString(mActive))
+        .set(AMEDIAMETRICS_PROP_STATUS, (int32_t)status)
         .set(AMEDIAMETRICS_PROP_SELECTEDMICDIRECTION, (int32_t)mSelectedMicDirection)
         .set(AMEDIAMETRICS_PROP_SELECTEDMICFIELDDIRECTION, (double)mSelectedMicFieldDimension)
         .record();
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index 4ff2cca..536101a 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -1975,6 +1975,7 @@
         .set(AMEDIAMETRICS_PROP_VOLUME_LEFT, (double)mVolume[AUDIO_INTERLEAVE_LEFT])
         .set(AMEDIAMETRICS_PROP_VOLUME_RIGHT, (double)mVolume[AUDIO_INTERLEAVE_RIGHT])
         .set(AMEDIAMETRICS_PROP_STATE, stateToString(mState))
+        .set(AMEDIAMETRICS_PROP_STATUS, (int32_t)NO_ERROR)
         .set(AMEDIAMETRICS_PROP_AUXEFFECTID, (int32_t)mAuxEffectId)
         .set(AMEDIAMETRICS_PROP_SAMPLERATE, (int32_t)mSampleRate)
         .set(AMEDIAMETRICS_PROP_PLAYBACK_SPEED, (double)mPlaybackRate.mSpeed)
@@ -2017,8 +2018,8 @@
     // Ensure these variables are initialized in set().
     mediametrics::LogItem(AMEDIAMETRICS_KEY_AUDIO_TRACK_ERROR)
         .set(AMEDIAMETRICS_PROP_EVENT, event)
-        .set(AMEDIAMETRICS_PROP_ERROR, mediametrics::statusToErrorString(status))
-        .set(AMEDIAMETRICS_PROP_ERRORMESSAGE, message)
+        .set(AMEDIAMETRICS_PROP_STATUS, (int32_t)status)
+        .set(AMEDIAMETRICS_PROP_STATUSMESSAGE, message)
         .set(AMEDIAMETRICS_PROP_ORIGINALFLAGS, toString(mOrigFlags).c_str())
         .set(AMEDIAMETRICS_PROP_SESSIONID, (int32_t)mSessionId)
         .set(AMEDIAMETRICS_PROP_CONTENTTYPE, toString(mAttributes.content_type).c_str())
diff --git a/media/libmediametrics/MediaMetricsItem.cpp b/media/libmediametrics/MediaMetricsItem.cpp
index 36ab8c3..57fc49d 100644
--- a/media/libmediametrics/MediaMetricsItem.cpp
+++ b/media/libmediametrics/MediaMetricsItem.cpp
@@ -57,19 +57,19 @@
     // This may be found in frameworks/av/media/libmediametrics/include/MediaMetricsConstants.h
     static std::unordered_map<std::string, int32_t> map{
         {"",                                      NO_ERROR},
-        {AMEDIAMETRICS_PROP_ERROR_VALUE_OK,       NO_ERROR},
-        {AMEDIAMETRICS_PROP_ERROR_VALUE_ARGUMENT, BAD_VALUE},
-        {AMEDIAMETRICS_PROP_ERROR_VALUE_IO,       DEAD_OBJECT},
-        {AMEDIAMETRICS_PROP_ERROR_VALUE_MEMORY,   NO_MEMORY},
-        {AMEDIAMETRICS_PROP_ERROR_VALUE_SECURITY, PERMISSION_DENIED},
-        {AMEDIAMETRICS_PROP_ERROR_VALUE_STATE,    INVALID_OPERATION},
-        {AMEDIAMETRICS_PROP_ERROR_VALUE_TIMEOUT,  WOULD_BLOCK},
-        {AMEDIAMETRICS_PROP_ERROR_VALUE_UNKNOWN,  UNKNOWN_ERROR},
+        {AMEDIAMETRICS_PROP_STATUS_VALUE_OK,       NO_ERROR},
+        {AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT, BAD_VALUE},
+        {AMEDIAMETRICS_PROP_STATUS_VALUE_IO,       DEAD_OBJECT},
+        {AMEDIAMETRICS_PROP_STATUS_VALUE_MEMORY,   NO_MEMORY},
+        {AMEDIAMETRICS_PROP_STATUS_VALUE_SECURITY, PERMISSION_DENIED},
+        {AMEDIAMETRICS_PROP_STATUS_VALUE_STATE,    INVALID_OPERATION},
+        {AMEDIAMETRICS_PROP_STATUS_VALUE_TIMEOUT,  WOULD_BLOCK},
+        {AMEDIAMETRICS_PROP_STATUS_VALUE_UNKNOWN,  UNKNOWN_ERROR},
     };
     return map;
 }
 
-status_t errorStringToStatus(const char *error) {
+status_t statusStringToStatus(const char *error) {
     const auto& map = getErrorStringMap();
     if (error == nullptr || error[0] == '\0') return NO_ERROR;
     auto it = map.find(error);
diff --git a/media/libmediametrics/include/MediaMetricsConstants.h b/media/libmediametrics/include/MediaMetricsConstants.h
index 7d120b5..2bf72a7 100644
--- a/media/libmediametrics/include/MediaMetricsConstants.h
+++ b/media/libmediametrics/include/MediaMetricsConstants.h
@@ -119,18 +119,6 @@
 #define AMEDIAMETRICS_PROP_DURATIONNS     "durationNs"     // int64 duration time span
 #define AMEDIAMETRICS_PROP_ENCODING       "encoding"       // string value of format
 
-// Error statistics
-#define AMEDIAMETRICS_PROP_ERROR          "error#"         // string, empty or one of
-                                                           // AMEDIAMETRICS_PROP_ERROR_VALUE_*
-                                                           // Used for error categorization.
-#define AMEDIAMETRICS_PROP_ERRORSUBCODE   "errorSubCode#"  // int32, specific code for error
-                                                           // used in conjunction with error#.
-#define AMEDIAMETRICS_PROP_ERRORMESSAGE   "errorMessage#"  // string, supplemental to error.
-                                                           // Arbitrary information treated as
-                                                           // informational, may be logcat msg,
-                                                           // or an exception with stack trace.
-                                                           // Treated as "debug" information.
-
 #define AMEDIAMETRICS_PROP_EVENT          "event#"         // string value (often func name)
 #define AMEDIAMETRICS_PROP_EXECUTIONTIMENS "executionTimeNs"  // time to execute the event
 
@@ -162,7 +150,17 @@
 #define AMEDIAMETRICS_PROP_STARTUPMS      "startupMs"      // double value
 // State is "ACTIVE" or "STOPPED" for AudioRecord
 #define AMEDIAMETRICS_PROP_STATE          "state"          // string
-#define AMEDIAMETRICS_PROP_STATUS         "status"         // int32 status_t
+#define AMEDIAMETRICS_PROP_STATUS         "status#"        // int32 status_t
+                                                           // AAudio uses their own status codes
+// Supplemental information to the status code.
+#define AMEDIAMETRICS_PROP_STATUSSUBCODE  "statusSubCode"  // int32, specific code
+                                                           // used in conjunction with status.
+#define AMEDIAMETRICS_PROP_STATUSMESSAGE  "statusMessage"  // string, supplemental info.
+                                                           // Arbitrary information treated as
+                                                           // informational, may be logcat msg,
+                                                           // or an exception with stack trace.
+                                                           // Treated as "debug" information.
+
 #define AMEDIAMETRICS_PROP_STREAMTYPE     "streamType"     // string (AudioTrack)
 #define AMEDIAMETRICS_PROP_THREADID       "threadId"       // int32 value io handle
 #define AMEDIAMETRICS_PROP_THROTTLEMS     "throttleMs"     // double
@@ -237,17 +235,20 @@
 // https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/include/binder/Status.h;drc=88e25c0861499ee3ab885814dddc097ab234cb7b;l=57
 // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/media/java/android/media/AudioSystem.java;drc=3ac246c43294d7f7012bdcb0ccb7bae1aa695bd4;l=785
 // https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/libaaudio/include/aaudio/AAudio.h;drc=cfd3a6fa3aaaf712a890dc02452b38ef401083b8;l=120
+// https://abseil.io/docs/cpp/guides/status-codes
 
 // Status errors:
 // An empty status string or "ok" is interpreted as no error.
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_OK                 "ok"
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_OK                "ok"
 
 // Error category: argument
 //   IllegalArgumentException
 //   NullPointerException
 //   BAD_VALUE
+//   absl::INVALID_ARGUMENT
+//   absl::OUT_OF_RANGE
 //   Out of range, out of bounds.
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_ARGUMENT           "argument"
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT          "argument"
 
 // Error category: io
 //   IOException
@@ -258,36 +259,48 @@
 //   file or ioctl failure
 //   Service, rpc, binder, or socket failure.
 //   Hardware or device failure.
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_IO                 "io"
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_IO                "io"
 
 // Error category: outOfMemory
 //   OutOfMemoryException
 //   NO_MEMORY
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_MEMORY             "memory"
+//   absl::RESOURCE_EXHAUSTED
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_MEMORY            "memory"
 
 // Error category: security
 //   SecurityException
 //   PERMISSION_DENIED
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_SECURITY           "security"
+//   absl::PERMISSION_DENIED
+//   absl::UNAUTHENTICATED
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_SECURITY          "security"
 
 // Error category: state
 //   IllegalStateException
 //   UnsupportedOperationException
 //   INVALID_OPERATION
 //   NO_INIT
+//   absl::NOT_FOUND
+//   absl::ALREADY_EXISTS
+//   absl::FAILED_PRECONDITION
+//   absl::UNAVAILABLE
+//   absl::UNIMPLEMENTED
 //   Functionality not implemented (argument may or may not be correct).
 //   Call unexpected or out of order.
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_STATE              "state"
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_STATE             "state"
 
 // Error category: timeout
 //   TimeoutException
 //   WOULD_BLOCK
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_TIMEOUT            "timeout"
+//   absl::DEADLINE_EXCEEDED
+//   absl::ABORTED
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_TIMEOUT           "timeout"
 
 // Error category: unknown
 //   Exception (Java specified not listed above, or custom app/service)
 //   UNKNOWN_ERROR
+//   absl::INTERNAL
+//   absl::DATA_LOSS
 //   Catch-all bucket for errors not listed above.
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_UNKNOWN            "unknown"
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_UNKNOWN           "unknown"
 
 #endif // ANDROID_MEDIA_MEDIAMETRICSCONSTANTS_H
diff --git a/media/libmediametrics/include/media/MediaMetricsItem.h b/media/libmediametrics/include/media/MediaMetricsItem.h
index 87f608f..de56665 100644
--- a/media/libmediametrics/include/media/MediaMetricsItem.h
+++ b/media/libmediametrics/include/media/MediaMetricsItem.h
@@ -106,34 +106,34 @@
 };
 
 /*
- * Helper for error conversions
+ * Helper for status conversions
  */
 
-static inline constexpr const char* statusToErrorString(status_t status) {
+inline constexpr const char* statusToStatusString(status_t status) {
     switch (status) {
-    case NO_ERROR:
-        return "";
     case BAD_VALUE:
-        return AMEDIAMETRICS_PROP_ERROR_VALUE_ARGUMENT;
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT;
     case DEAD_OBJECT:
     case FAILED_TRANSACTION:
-        return AMEDIAMETRICS_PROP_ERROR_VALUE_IO;
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_IO;
     case NO_MEMORY:
-        return AMEDIAMETRICS_PROP_ERROR_VALUE_MEMORY;
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_MEMORY;
     case PERMISSION_DENIED:
-        return AMEDIAMETRICS_PROP_ERROR_VALUE_SECURITY;
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_SECURITY;
     case NO_INIT:
     case INVALID_OPERATION:
-        return AMEDIAMETRICS_PROP_ERROR_VALUE_STATE;
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_STATE;
     case WOULD_BLOCK:
-        return AMEDIAMETRICS_PROP_ERROR_VALUE_TIMEOUT;
-    case UNKNOWN_ERROR:
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_TIMEOUT;
     default:
-        return AMEDIAMETRICS_PROP_ERROR_VALUE_UNKNOWN;
+        if (status >= 0) return AMEDIAMETRICS_PROP_STATUS_VALUE_OK; // non-negative values "OK"
+        [[fallthrough]];            // negative values are error.
+    case UNKNOWN_ERROR:
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_UNKNOWN;
     }
 }
 
-status_t errorStringToStatus(const char *error);
+status_t statusStringToStatus(const char *error);
 
 /*
  * Time printing
diff --git a/services/mediametrics/Android.bp b/services/mediametrics/Android.bp
index 0351d2d..b2c9465 100644
--- a/services/mediametrics/Android.bp
+++ b/services/mediametrics/Android.bp
@@ -183,6 +183,10 @@
         "libplatformprotos",
     ],
 
+    header_libs: [
+        "libaaudio_headers",
+    ],
+
     include_dirs: [
         "system/media/audio_utils/include",
     ],
diff --git a/services/mediametrics/AudioAnalytics.cpp b/services/mediametrics/AudioAnalytics.cpp
index 270fe2f..21768f8 100644
--- a/services/mediametrics/AudioAnalytics.cpp
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -21,6 +21,7 @@
 
 #include "AudioAnalytics.h"
 
+#include <aaudio/AAudio.h>        // error codes
 #include <audio_utils/clock.h>    // clock conversions
 #include <cutils/properties.h>
 #include <statslog.h>             // statsd
@@ -64,6 +65,59 @@
     }
 }
 
+// The status variable contains status_t codes which are used by
+// the core audio framework.
+//
+// We also consider AAudio status codes as they are non-overlapping with status_t
+// and compiler checked here.
+//
+// Caution: As AAUDIO_ERROR codes have a unique range (AAUDIO_ERROR_BASE = -900),
+// overlap with status_t should not present an issue.
+//
+// See: system/core/libutils/include/utils/Errors.h
+//      frameworks/av/media/libaaudio/include/aaudio/AAudio.h
+//
+// Compare with mediametrics::statusToStatusString
+//
+inline constexpr const char* extendedStatusToStatusString(status_t status) {
+    switch (status) {
+    case BAD_VALUE:           // status_t
+    case AAUDIO_ERROR_ILLEGAL_ARGUMENT:
+    case AAUDIO_ERROR_INVALID_FORMAT:
+    case AAUDIO_ERROR_INVALID_RATE:
+    case AAUDIO_ERROR_NULL:
+    case AAUDIO_ERROR_OUT_OF_RANGE:
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT;
+    case DEAD_OBJECT:         // status_t
+    case FAILED_TRANSACTION:  // status_t
+    case AAUDIO_ERROR_DISCONNECTED:
+    case AAUDIO_ERROR_INVALID_HANDLE:
+    case AAUDIO_ERROR_NO_SERVICE:
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_IO;
+    case NO_MEMORY:           // status_t
+    case AAUDIO_ERROR_NO_FREE_HANDLES:
+    case AAUDIO_ERROR_NO_MEMORY:
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_MEMORY;
+    case PERMISSION_DENIED:   // status_t
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_SECURITY;
+    case INVALID_OPERATION:   // status_t
+    case NO_INIT:             // status_t
+    case AAUDIO_ERROR_INVALID_STATE:
+    case AAUDIO_ERROR_UNAVAILABLE:
+    case AAUDIO_ERROR_UNIMPLEMENTED:
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_STATE;
+    case WOULD_BLOCK:         // status_t
+    case AAUDIO_ERROR_TIMEOUT:
+    case AAUDIO_ERROR_WOULD_BLOCK:
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_TIMEOUT;
+    default:
+        if (status >= 0) return AMEDIAMETRICS_PROP_STATUS_VALUE_OK; // non-negative values "OK"
+        [[fallthrough]];            // negative values are error.
+    case UNKNOWN_ERROR:       // status_t
+        return AMEDIAMETRICS_PROP_STATUS_VALUE_UNKNOWN;
+    }
+}
+
 static constexpr const auto LOG_LEVEL = android::base::VERBOSE;
 
 static constexpr int PREVIOUS_STATE_EXPIRE_SEC = 60 * 60; // 1 hour.
@@ -392,11 +446,15 @@
 {
     if (!startsWith(item->getKey(), AMEDIAMETRICS_KEY_PREFIX_AUDIO)) return BAD_VALUE;
     status_t status = mAnalyticsState->submit(item, isTrusted);
+
+    // Status is selectively authenticated.
+    processStatus(item);
+
     if (status != NO_ERROR) return status;  // may not be permitted.
 
     // Only if the item was successfully submitted (permission)
     // do we check triggered actions.
-    checkActions(item);
+    processActions(item);
     return NO_ERROR;
 }
 
@@ -430,7 +488,7 @@
     return { ss.str(), lines - ll };
 }
 
-void AudioAnalytics::checkActions(const std::shared_ptr<const mediametrics::Item>& item)
+void AudioAnalytics::processActions(const std::shared_ptr<const mediametrics::Item>& item)
 {
     auto actions = mActions.getActionsForItem(item); // internally locked.
     // Execute actions with no lock held.
@@ -439,6 +497,36 @@
     }
 }
 
+void AudioAnalytics::processStatus(const std::shared_ptr<const mediametrics::Item>& item)
+{
+    int32_t status;
+    if (!item->get(AMEDIAMETRICS_PROP_STATUS, &status)) return;
+
+    // Any record with a status will automatically be added to a heat map.
+    // Standard information.
+    const auto key = item->getKey();
+    const auto uid = item->getUid();
+
+    // from audio.track.10 ->  prefix = audio.track, suffix = 10
+    // from audio.track.error -> prefix = audio.track, suffix = error
+    const auto [prefixKey, suffixKey] = stringutils::splitPrefixKey(key);
+
+    std::string message;
+    item->get(AMEDIAMETRICS_PROP_STATUSMESSAGE, &message); // optional
+
+    int32_t subCode = 0; // not used
+    (void)item->get(AMEDIAMETRICS_PROP_STATUSSUBCODE, &subCode); // optional
+
+    std::string eventStr; // optional
+    item->get(AMEDIAMETRICS_PROP_EVENT, &eventStr);
+
+    const std::string statusString = extendedStatusToStatusString(status);
+
+    // Add to the heat map - we automatically track every item's status to see
+    // the types of errors and the frequency of errors.
+    mHeatMap.add(prefixKey, suffixKey, eventStr, statusString, uid, message, subCode);
+}
+
 // HELPER METHODS
 
 std::string AudioAnalytics::getThreadFromTrack(const std::string& track) const
diff --git a/services/mediametrics/AudioAnalytics.h b/services/mediametrics/AudioAnalytics.h
index bcc407a..9b54cf3 100644
--- a/services/mediametrics/AudioAnalytics.h
+++ b/services/mediametrics/AudioAnalytics.h
@@ -109,11 +109,18 @@
      */
 
     /**
-     * Checks for any pending actions for a particular item.
+     * Processes any pending actions for a particular item.
      *
      * \param item to check against the current AnalyticsActions.
      */
-    void checkActions(const std::shared_ptr<const mediametrics::Item>& item);
+    void processActions(const std::shared_ptr<const mediametrics::Item>& item);
+
+    /**
+     * Processes status information contained in the item.
+     *
+     * \param item to check against for status handling
+     */
+    void processStatus(const std::shared_ptr<const mediametrics::Item>& item);
 
     // HELPER METHODS
     /**
diff --git a/services/mediametrics/AudioTypes.cpp b/services/mediametrics/AudioTypes.cpp
index 838cdd5..b67967b 100644
--- a/services/mediametrics/AudioTypes.cpp
+++ b/services/mediametrics/AudioTypes.cpp
@@ -15,8 +15,10 @@
  */
 
 #include "AudioTypes.h"
+#include "MediaMetricsConstants.h"
 #include "StringUtils.h"
 #include <media/TypeConverter.h> // requires libmedia_helper to get the Audio code.
+#include <statslog.h>            // statsd
 
 namespace android::mediametrics::types {
 
diff --git a/services/mediametrics/HeatMap.h b/services/mediametrics/HeatMap.h
index 230d1ad..950501a 100644
--- a/services/mediametrics/HeatMap.h
+++ b/services/mediametrics/HeatMap.h
@@ -81,7 +81,7 @@
             size_t ok = 0;
             size_t error = 0;
             for (const auto &[name, count] : eventPair.second) {
-                if (name == AMEDIAMETRICS_PROP_ERROR_VALUE_OK) {
+                if (name == AMEDIAMETRICS_PROP_STATUS_VALUE_OK) {
                     ok += count;
                 } else {
                     error += count;
diff --git a/services/mediametrics/StringUtils.h b/services/mediametrics/StringUtils.h
index 01034d9..a56f5b8 100644
--- a/services/mediametrics/StringUtils.h
+++ b/services/mediametrics/StringUtils.h
@@ -167,4 +167,41 @@
     return ss.str();
 }
 
+/**
+ * Returns true if the string is non-null, not empty, and contains only digits.
+ */
+inline constexpr bool isNumeric(const char *s)
+{
+    if (s == nullptr || *s == 0) return false;
+    do {
+        if (!isdigit(*s)) return false;
+    } while (*++s != 0);
+    return true;  // all digits
+}
+
+/**
+ * Extracts out the prefix from the key, returning a pair of prefix, suffix.
+ *
+ * Usually the key is something like:
+ * Prefix.(ID)
+ *   where ID is an integer,
+ *               or "error" if the id was not returned because of failure,
+ *               or "status" if general status.
+ *
+ * Example: audio.track.10     -> prefix = audio.track, suffix = 10
+ *          audio.track.error  -> prefix = audio.track, suffix = error
+ *          audio.track.status -> prefix = audio.track, suffix = status
+ *          audio.mute         -> prefix = audio.mute,  suffix = ""
+ */
+inline std::pair<std::string /* prefix */,
+                 std::string /* suffix */> splitPrefixKey(const std::string &key)
+{
+    const size_t split = key.rfind('.');
+    const char* suffix = key.c_str() + split + 1;
+    if (*suffix && (!strcmp(suffix, "error") || !strcmp(suffix, "status") || isNumeric(suffix))) {
+        return { key.substr(0, split), suffix };
+    }
+    return { key, "" };
+}
+
 } // namespace android::mediametrics::stringutils
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
index d334676..102700a 100644
--- a/services/mediametrics/tests/mediametrics_tests.cpp
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -1226,8 +1226,8 @@
     }
 }
 
-TEST(mediametrics_tests, ErrorConversion) {
-    constexpr status_t errors[] = {
+TEST(mediametrics_tests, StatusConversion) {
+    constexpr status_t statuses[] = {
         NO_ERROR,
         BAD_VALUE,
         DEAD_OBJECT,
@@ -1239,13 +1239,13 @@
     };
 
     auto roundTrip = [](status_t status) {
-        return android::mediametrics::errorStringToStatus(
-                android::mediametrics::statusToErrorString(status));
+        return android::mediametrics::statusStringToStatus(
+                android::mediametrics::statusToStatusString(status));
     };
 
     // Primary status error categories.
-    for (const auto error : errors) {
-        ASSERT_EQ(error, roundTrip(error));
+    for (const auto status : statuses) {
+        ASSERT_EQ(status, roundTrip(status));
     }
 
     // Status errors specially considered.
@@ -1260,20 +1260,20 @@
 
     ASSERT_EQ((size_t)0, heatMap.size());
     heatMap.add("someKey", "someSuffix", "someEvent",
-            AMEDIAMETRICS_PROP_ERROR_VALUE_OK, UID, "message", SUBCODE);
+            AMEDIAMETRICS_PROP_STATUS_VALUE_OK, UID, "message", SUBCODE);
     ASSERT_EQ((size_t)1, heatMap.size());
     heatMap.add("someKey", "someSuffix", "someEvent",
-            AMEDIAMETRICS_PROP_ERROR_VALUE_OK, UID, "message", SUBCODE);
+            AMEDIAMETRICS_PROP_STATUS_VALUE_OK, UID, "message", SUBCODE);
     heatMap.add("someKey", "someSuffix", "anotherEvent",
-            AMEDIAMETRICS_PROP_ERROR_VALUE_ARGUMENT, UID, "message", SUBCODE);
+            AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT, UID, "message", SUBCODE);
     ASSERT_EQ((size_t)1, heatMap.size());
     heatMap.add("anotherKey", "someSuffix", "someEvent",
-            AMEDIAMETRICS_PROP_ERROR_VALUE_OK, UID, "message", SUBCODE);
+            AMEDIAMETRICS_PROP_STATUS_VALUE_OK, UID, "message", SUBCODE);
     ASSERT_EQ((size_t)2, heatMap.size());
     ASSERT_EQ((size_t)0, heatMap.rejected());
 
     heatMap.add("thirdKey", "someSuffix", "someEvent",
-            AMEDIAMETRICS_PROP_ERROR_VALUE_OK, UID, "message", SUBCODE);
+            AMEDIAMETRICS_PROP_STATUS_VALUE_OK, UID, "message", SUBCODE);
     ASSERT_EQ((size_t)2, heatMap.size());
     ASSERT_EQ((size_t)1, heatMap.rejected());