MediaMetrics: Add stack allocated Item

Enable byte string submit for low overhead.
Add tests.

Test: atest mediametrics_tests
Bug: 138583596
Change-Id: Idce65fd270e8ad45ba754a660734368416c8d1b5
diff --git a/media/libmediametrics/IMediaAnalyticsService.cpp b/media/libmediametrics/IMediaAnalyticsService.cpp
index 4324f6d..534aece 100644
--- a/media/libmediametrics/IMediaAnalyticsService.cpp
+++ b/media/libmediametrics/IMediaAnalyticsService.cpp
@@ -34,8 +34,11 @@
 
 namespace android {
 
+// TODO: Currently ONE_WAY transactions, make both ONE_WAY and synchronous options.
+
 enum {
-    SUBMIT_ITEM_ONEWAY = IBinder::FIRST_CALL_TRANSACTION,
+    SUBMIT_ITEM = IBinder::FIRST_CALL_TRANSACTION,
+    SUBMIT_BUFFER,
 };
 
 class BpMediaAnalyticsService: public BpInterface<IMediaAnalyticsService>
@@ -62,7 +65,30 @@
         }
 
         status = remote()->transact(
-                SUBMIT_ITEM_ONEWAY, data, nullptr /* reply */, IBinder::FLAG_ONEWAY);
+                SUBMIT_ITEM, data, nullptr /* reply */, IBinder::FLAG_ONEWAY);
+        ALOGW_IF(status != NO_ERROR, "%s: bad response from service for submit, status=%d",
+                __func__, status);
+        return status;
+    }
+
+    status_t submitBuffer(const char *buffer, size_t length) override
+    {
+        if (buffer == nullptr || length > INT32_MAX) {
+            return BAD_VALUE;
+        }
+        ALOGV("%s: (ONEWAY) length:%zu", __func__, length);
+
+        Parcel data;
+        data.writeInterfaceToken(IMediaAnalyticsService::getInterfaceDescriptor());
+
+        status_t status = data.writeInt32(length)
+                ?: data.write((uint8_t*)buffer, length);
+        if (status != NO_ERROR) {
+            return status;
+        }
+
+        status = remote()->transact(
+                SUBMIT_BUFFER, data, nullptr /* reply */, IBinder::FLAG_ONEWAY);
         ALOGW_IF(status != NO_ERROR, "%s: bad response from service for submit, status=%d",
                 __func__, status);
         return status;
@@ -76,10 +102,8 @@
 status_t BnMediaAnalyticsService::onTransact(
     uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
 {
-    const int clientPid = IPCThreadState::self()->getCallingPid();
-
     switch (code) {
-    case SUBMIT_ITEM_ONEWAY: {
+    case SUBMIT_ITEM: {
         CHECK_INTERFACE(IMediaAnalyticsService, data, reply);
 
         MediaAnalyticsItem * const item = MediaAnalyticsItem::create();
@@ -87,12 +111,25 @@
         if (status != NO_ERROR) { // assume failure logged in item
             return status;
         }
-        // TODO: remove this setPid.
-        item->setPid(clientPid);
         status = submitInternal(item, true /* release */);
         // assume failure logged by submitInternal
         return NO_ERROR;
-    } break;
+    }
+    case SUBMIT_BUFFER: {
+        CHECK_INTERFACE(IMediaAnalyticsService, data, reply);
+        int32_t length;
+        status_t status = data.readInt32(&length);
+        if (status != NO_ERROR || length <= 0) {
+            return BAD_VALUE;
+        }
+        const void *ptr = data.readInplace(length);
+        if (ptr == nullptr) {
+            return BAD_VALUE;
+        }
+        status = submitBuffer(static_cast<const char *>(ptr), length);
+        // assume failure logged by submitBuffer
+        return NO_ERROR;
+    }
 
     default:
         return BBinder::onTransact(code, data, reply, flags);
diff --git a/media/libmediametrics/MediaAnalyticsItem.cpp b/media/libmediametrics/MediaAnalyticsItem.cpp
index 14dce79..8025e49 100644
--- a/media/libmediametrics/MediaAnalyticsItem.cpp
+++ b/media/libmediametrics/MediaAnalyticsItem.cpp
@@ -35,6 +35,10 @@
 #include <media/MediaAnalyticsItem.h>
 #include <private/android_filesystem_config.h>
 
+// Max per-property string size before truncation in toString().
+// Do not make too large, as this is used for dumpsys purposes.
+static constexpr size_t kMaxPropertyStringSize = 4096;
+
 namespace android {
 
 #define DEBUG_SERVICEACCESS     0
@@ -367,70 +371,18 @@
 }
 
 std::string MediaAnalyticsItem::toString(int version) const {
-
-    // v0 : released with 'o'
-    // v1 : bug fix (missing pid/finalized separator),
-    //      adds apk name, apk version code
-
-    if (version <= PROTO_FIRST) {
-        // default to original v0 format, until proper parsers are in place
-        version = PROTO_V0;
-    } else if (version > PROTO_LAST) {
-        version = PROTO_LAST;
-    }
-
     std::string result;
-    char buffer[512];
+    char buffer[kMaxPropertyStringSize];
 
-    if (version == PROTO_V0) {
-        result = "(";
-    } else {
-        snprintf(buffer, sizeof(buffer), "[%d:", version);
-        result.append(buffer);
-    }
-
-    // same order as we spill into the parcel, although not required
-    // key+session are our primary matching criteria
-    result.append(mKey.c_str());
-    result.append(":0:"); // sessionID
-
-    snprintf(buffer, sizeof(buffer), "%d:", mUid);
+    snprintf(buffer, sizeof(buffer), "[%d:%s:%d:%d:%lld:%s:%zu:",
+            version, mKey.c_str(), mPid, mUid, (long long)mTimestamp,
+            mPkgName.c_str(), mPropCount);
     result.append(buffer);
-
-    if (version >= PROTO_V1) {
-        result.append(mPkgName);
-        snprintf(buffer, sizeof(buffer), ":%"  PRId64 ":", mPkgVersionCode);
-        result.append(buffer);
-    }
-
-    // in 'o' (v1) , the separator between pid and finalized was omitted
-    if (version <= PROTO_V0) {
-        snprintf(buffer, sizeof(buffer), "%d", mPid);
-    } else {
-        snprintf(buffer, sizeof(buffer), "%d:", mPid);
-    }
-    result.append(buffer);
-
-    snprintf(buffer, sizeof(buffer), "%d:", 0 /* finalized */); // TODO: remove this.
-    result.append(buffer);
-    snprintf(buffer, sizeof(buffer), "%" PRId64 ":", mTimestamp);
-    result.append(buffer);
-
-    // set of items
-    int count = mPropCount;
-    snprintf(buffer, sizeof(buffer), "%d:", count);
-    result.append(buffer);
-    for (int i = 0 ; i < count; i++ ) {
+    for (size_t i = 0 ; i < mPropCount; ++i) {
         mProps[i].toString(buffer, sizeof(buffer));
         result.append(buffer);
     }
-
-    if (version == PROTO_V0) {
-        result.append(")");
-    } else {
-        result.append("]");
-    }
-
+    result.append("]");
     return result;
 }
 
@@ -451,8 +403,9 @@
     }
 }
 
+namespace mediametrics {
 //static
-bool MediaAnalyticsItem::isEnabled() {
+bool BaseItem::isEnabled() {
     // completely skip logging from certain UIDs. We do this here
     // to avoid the multi-second timeouts while we learn that
     // sepolicy will not let us find the service.
@@ -481,25 +434,47 @@
 class MediaMetricsDeathNotifier : public IBinder::DeathRecipient {
         virtual void binderDied(const wp<IBinder> &) {
             ALOGW("Reacquire service connection on next request");
-            MediaAnalyticsItem::dropInstance();
+            BaseItem::dropInstance();
         }
 };
 
 static sp<MediaMetricsDeathNotifier> sNotifier;
 // static
-sp<IMediaAnalyticsService> MediaAnalyticsItem::sAnalyticsService;
+sp<IMediaAnalyticsService> BaseItem::sAnalyticsService;
 static std::mutex sServiceMutex;
 static int sRemainingBindAttempts = SVC_TRIES;
 
 // static
-void MediaAnalyticsItem::dropInstance() {
+void BaseItem::dropInstance() {
     std::lock_guard  _l(sServiceMutex);
     sRemainingBindAttempts = SVC_TRIES;
     sAnalyticsService = nullptr;
 }
 
+// static
+bool BaseItem::submitBuffer(const char *buffer, size_t size) {
+/*
+    MediaAnalyticsItem item;
+    status_t status = item.readFromByteString(buffer, size);
+    ALOGD("%s: status:%d, size:%zu, item:%s", __func__, status, size, item.toString().c_str());
+    return item.selfrecord();
+    */
+
+    ALOGD_IF(DEBUG_API, "%s: delivering %zu bytes", __func__, size);
+    sp<IMediaAnalyticsService> svc = getInstance();
+    if (svc != nullptr) {
+        const status_t status = svc->submitBuffer(buffer, size);
+        if (status != NO_ERROR) {
+            ALOGW("%s: failed(%d) to record: %zu bytes", __func__, status, size);
+            return false;
+        }
+        return true;
+    }
+    return false;
+}
+
 //static
-sp<IMediaAnalyticsService> MediaAnalyticsItem::getInstance() {
+sp<IMediaAnalyticsService> BaseItem::getInstance() {
     static const char *servicename = "media.metrics";
     static const bool enabled = isEnabled(); // singleton initialized
 
@@ -537,6 +512,8 @@
     return sAnalyticsService;
 }
 
+} // namespace mediametrics
+
 // merge the info from 'incoming' into this record.
 // we finish with a union of this+incoming and special handling for collisions
 bool MediaAnalyticsItem::merge(MediaAnalyticsItem *incoming) {
@@ -633,6 +610,7 @@
     while (*ptr != 0) {
         if (ptr >= bufferptrmax) {
             ALOGE("%s: buffer exceeded", __func__);
+            return BAD_VALUE;
         }
         ++ptr;
     }
@@ -657,39 +635,41 @@
         return INVALID_OPERATION;
     }
     const uint16_t version = 0;
-    const uint32_t header_len =
-        sizeof(uint32_t)     // overall length
-        + sizeof(header_len) // header length
-        + sizeof(version)    // encoding version
-        + sizeof(uint16_t)   // key length
+    const uint32_t header_size =
+        sizeof(uint32_t)      // total size
+        + sizeof(header_size) // header size
+        + sizeof(version)     // encoding version
+        + sizeof(uint16_t)    // key size
         + keySizeZeroTerminated // key, zero terminated
-        + sizeof(int32_t)    // pid
-        + sizeof(int32_t)    // uid
-        + sizeof(int64_t)    // timestamp
+        + sizeof(int32_t)     // pid
+        + sizeof(int32_t)     // uid
+        + sizeof(int64_t)     // timestamp
         ;
 
-    uint32_t len = header_len
+    uint32_t size = header_size
         + sizeof(uint32_t) // # properties
         ;
     for (size_t i = 0 ; i < mPropCount; ++i) {
-        const size_t size = mProps[i].getByteStringSize();
-        if (size > UINT_MAX - 1) {
-            ALOGW("%s: prop %zu has size %zu", __func__, i, size);
+        const size_t propSize = mProps[i].getByteStringSize();
+        if (propSize > UINT16_MAX) {
+            ALOGW("%s: prop %zu size %zu too large", __func__, i, propSize);
             return INVALID_OPERATION;
         }
-        len += size;
+        if (__builtin_add_overflow(size, propSize, &size)) {
+            ALOGW("%s: item size overflow at property %zu", __func__, i);
+            return INVALID_OPERATION;
+        }
     }
 
-    // TODO: consider package information and timestamp.
-
-    // now that we have a size... let's allocate and fill
-    char *build = (char *)calloc(1 /* nmemb */, len);
+    // since we fill every byte in the buffer (there is no padding),
+    // malloc is used here instead of calloc.
+    char * const build = (char *)malloc(size);
     if (build == nullptr) return NO_MEMORY;
 
     char *filling = build;
-    char *buildmax = build + len;
-    if (insert(len, &filling, buildmax) != NO_ERROR
-            || insert(header_len, &filling, buildmax) != NO_ERROR
+    char *buildmax = build + size;
+    if (insert((uint32_t)size, &filling, buildmax) != NO_ERROR
+            || insert(header_size, &filling, buildmax) != NO_ERROR
             || insert(version, &filling, buildmax) != NO_ERROR
             || insert((uint16_t)keySizeZeroTerminated, &filling, buildmax) != NO_ERROR
             || insert(mKey.c_str(), &filling, buildmax) != NO_ERROR
@@ -697,26 +677,27 @@
             || insert((int32_t)mUid, &filling, buildmax) != NO_ERROR
             || insert((int64_t)mTimestamp, &filling, buildmax) != NO_ERROR
             || insert((uint32_t)mPropCount, &filling, buildmax) != NO_ERROR) {
-        ALOGD("%s:could not write header", __func__);
+        ALOGE("%s:could not write header", __func__);  // shouldn't happen
         free(build);
         return INVALID_OPERATION;
     }
     for (size_t i = 0 ; i < mPropCount; ++i) {
         if (mProps[i].writeToByteString(&filling, buildmax) != NO_ERROR) {
             free(build);
-            ALOGD("%s:could not write prop %zu of %zu", __func__, i, mPropCount);
+            // shouldn't happen
+            ALOGE("%s:could not write prop %zu of %zu", __func__, i, mPropCount);
             return INVALID_OPERATION;
         }
     }
 
     if (filling != buildmax) {
-        ALOGE("problems populating; wrote=%d planned=%d",
-              (int)(filling - build), len);
+        ALOGE("%s: problems populating; wrote=%d planned=%d",
+                __func__, (int)(filling - build), (int)size);
         free(build);
         return INVALID_OPERATION;
     }
     *pbuffer = build;
-    *plength = len;
+    *plength = size;
     return NO_ERROR;
 }
 
@@ -727,40 +708,41 @@
     const char *read = bufferptr;
     const char *readend = bufferptr + length;
 
-    uint32_t len;
-    uint32_t header_len;
-    int16_t version;
-    int16_t key_len;
+    uint32_t size;
+    uint32_t header_size;
+    uint16_t version;
+    uint16_t key_size;
     char *key = nullptr;
     int32_t pid;
     int32_t uid;
     int64_t timestamp;
     uint32_t propCount;
-    if (extract(&len, &read, readend) != NO_ERROR
-            || extract(&header_len, &read, readend) != NO_ERROR
+    if (extract(&size, &read, readend) != NO_ERROR
+            || extract(&header_size, &read, readend) != NO_ERROR
             || extract(&version, &read, readend) != NO_ERROR
-            || extract(&key_len, &read, readend) != NO_ERROR
+            || extract(&key_size, &read, readend) != NO_ERROR
             || extract(&key, &read, readend) != NO_ERROR
             || extract(&pid, &read, readend) != NO_ERROR
             || extract(&uid, &read, readend) != NO_ERROR
             || extract(&timestamp, &read, readend) != NO_ERROR
-            || len > length
-            || header_len > len) {
+            || size > length
+            || strlen(key) + 1 != key_size
+            || header_size > size) {
         free(key);
-        ALOGD("%s: invalid header", __func__);
+        ALOGW("%s: invalid header", __func__);
         return INVALID_OPERATION;
     }
     mKey = key;
     free(key);
     const size_t pos = read - bufferptr;
-    if (pos > header_len) {
-        ALOGD("%s: invalid header pos:%zu > header_len:%u",
-                __func__, pos, header_len);
+    if (pos > header_size) {
+        ALOGW("%s: invalid header pos:%zu > header_size:%u",
+                __func__, pos, header_size);
         return INVALID_OPERATION;
-    } else if (pos < header_len) {
-        ALOGD("%s: mismatched header pos:%zu < header_len:%u, advancing",
-                __func__, pos, header_len);
-        read += (header_len - pos);
+    } else if (pos < header_size) {
+        ALOGW("%s: mismatched header pos:%zu < header_size:%u, advancing",
+                __func__, pos, header_size);
+        read += (header_size - pos);
     }
     if (extract(&propCount, &read, readend) != NO_ERROR) {
         ALOGD("%s: cannot read prop count", __func__);
@@ -772,7 +754,7 @@
     for (size_t i = 0; i < propCount; ++i) {
         Prop *prop = allocateProp();
         if (prop->readFromByteString(&read, readend) != NO_ERROR) {
-            ALOGD("%s: cannot read prop %zu", __func__, i);
+            ALOGW("%s: cannot read prop %zu", __func__, i);
             return INVALID_OPERATION;
         }
     }
@@ -910,8 +892,10 @@
     return header + payload;
 }
 
+namespace mediametrics {
+
 // TODO: fold into a template later.
-status_t MediaAnalyticsItem::writeToByteString(
+status_t BaseItem::writeToByteString(
         const char *name, int32_t value, char **bufferpptr, char *bufferptrmax)
 {
     const size_t len = 2 + 1 + strlen(name) + 1 + sizeof(value);
@@ -922,7 +906,7 @@
             ?: insert(value, bufferpptr, bufferptrmax);
 }
 
-status_t MediaAnalyticsItem::writeToByteString(
+status_t BaseItem::writeToByteString(
         const char *name, int64_t value, char **bufferpptr, char *bufferptrmax)
 {
     const size_t len = 2 + 1 + strlen(name) + 1 + sizeof(value);
@@ -933,7 +917,7 @@
             ?: insert(value, bufferpptr, bufferptrmax);
 }
 
-status_t MediaAnalyticsItem::writeToByteString(
+status_t BaseItem::writeToByteString(
         const char *name, double value, char **bufferpptr, char *bufferptrmax)
 {
     const size_t len = 2 + 1 + strlen(name) + 1 + sizeof(value);
@@ -944,7 +928,7 @@
             ?: insert(value, bufferpptr, bufferptrmax);
 }
 
-status_t MediaAnalyticsItem::writeToByteString(
+status_t BaseItem::writeToByteString(
         const char *name, const std::pair<int64_t, int64_t> &value, char **bufferpptr, char *bufferptrmax)
 {
     const size_t len = 2 + 1 + strlen(name) + 1 + 8 + 8;
@@ -956,9 +940,15 @@
             ?: insert(value.second, bufferpptr, bufferptrmax);
 }
 
-status_t MediaAnalyticsItem::writeToByteString(
+status_t BaseItem::writeToByteString(
         const char *name, char * const &value, char **bufferpptr, char *bufferptrmax)
 {
+    return writeToByteString(name, (const char *)value, bufferpptr, bufferptrmax);
+}
+
+status_t BaseItem::writeToByteString(
+        const char *name, const char * const &value, char **bufferpptr, char *bufferptrmax)
+{
     const size_t len = 2 + 1 + strlen(name) + 1 + strlen(value) + 1;
     if (len > UINT16_MAX) return BAD_VALUE;
     return insert((uint16_t)len, bufferpptr, bufferptrmax)
@@ -967,7 +957,8 @@
             ?: insert(value, bufferpptr, bufferptrmax);
 }
 
-status_t MediaAnalyticsItem::writeToByteString(
+
+status_t BaseItem::writeToByteString(
         const char *name, const none_t &, char **bufferpptr, char *bufferptrmax)
 {
     const size_t len = 2 + 1 + strlen(name) + 1;
@@ -977,22 +968,24 @@
             ?: insert(name, bufferpptr, bufferptrmax);
 }
 
+} // namespace mediametrics
+
 status_t MediaAnalyticsItem::Prop::writeToByteString(
         char **bufferpptr, char *bufferptrmax) const
 {
     switch (mType) {
     case kTypeInt32:
-        return MediaAnalyticsItem::writeToByteString(mName, u.int32Value, bufferpptr, bufferptrmax);
+        return BaseItem::writeToByteString(mName, u.int32Value, bufferpptr, bufferptrmax);
     case kTypeInt64:
-        return MediaAnalyticsItem::writeToByteString(mName, u.int64Value, bufferpptr, bufferptrmax);
+        return BaseItem::writeToByteString(mName, u.int64Value, bufferpptr, bufferptrmax);
     case kTypeDouble:
-        return MediaAnalyticsItem::writeToByteString(mName, u.doubleValue, bufferpptr, bufferptrmax);
+        return BaseItem::writeToByteString(mName, u.doubleValue, bufferpptr, bufferptrmax);
     case kTypeRate:
-        return MediaAnalyticsItem::writeToByteString(mName, u.rate, bufferpptr, bufferptrmax);
+        return BaseItem::writeToByteString(mName, u.rate, bufferpptr, bufferptrmax);
     case kTypeCString:
-        return MediaAnalyticsItem::writeToByteString(mName, u.CStringValue, bufferpptr, bufferptrmax);
+        return BaseItem::writeToByteString(mName, u.CStringValue, bufferpptr, bufferptrmax);
     case kTypeNone:
-        return MediaAnalyticsItem::writeToByteString(mName, none_t{}, bufferpptr, bufferptrmax);
+        return BaseItem::writeToByteString(mName, none_t{}, bufferpptr, bufferptrmax);
     default:
         ALOGE("%s: found bad prop type: %d, name %s",
                 __func__, mType, mName);  // no payload sent
diff --git a/media/libmediametrics/include/IMediaAnalyticsService.h b/media/libmediametrics/include/IMediaAnalyticsService.h
index 1453b5f..30c63e7 100644
--- a/media/libmediametrics/include/IMediaAnalyticsService.h
+++ b/media/libmediametrics/include/IMediaAnalyticsService.h
@@ -49,6 +49,8 @@
                may be silent and return 0 - success).
      */
     virtual status_t submit(MediaAnalyticsItem *item) = 0;
+
+    virtual status_t submitBuffer(const char *buffer, size_t length) = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/media/libmediametrics/include/MediaAnalyticsItem.h b/media/libmediametrics/include/MediaAnalyticsItem.h
index 5558211..bff7c9b 100644
--- a/media/libmediametrics/include/MediaAnalyticsItem.h
+++ b/media/libmediametrics/include/MediaAnalyticsItem.h
@@ -36,50 +36,346 @@
 class Parcel;
 
 /*
- * Media Metrics
- * Byte String format for communication of MediaAnalyticsItem.
+ * MediaMetrics Item
  *
- * .... begin of item
- * .... begin of header
- * (uint32) length: including the length field itself
- * (uint32) header length, including header_length and length fields.
- * (uint16) version: 0
- * (uint16) key length, including zero termination
- * (int8)+ key string, including 0 termination
+ * Byte string format.
+ *
+ * For Java
+ *  int64 corresponds to long
+ *  int32, uint32 corresponds to int
+ *  uint16 corresponds to char
+ *  uint8, int8 corresponds to byte
+ *
+ * Hence uint8 and uint32 values are limited to INT8_MAX and INT32_MAX.
+ *
+ * Physical layout of integers and doubles within the MediaMetrics byte string
+ * is in Native / host order, which is nearly always little endian.
+ *
+ * -- begin of item
+ * -- begin of header
+ * (uint32) item size: including the item size field
+ * (uint32) header size, including the item size and header size fields.
+ * (uint16) version: exactly 0
+ * (uint16) key size, that is key strlen + 1 for zero termination.
+ * (int8)+ key string which is 0 terminated
  * (int32) pid
  * (int32) uid
  * (int64) timestamp
- * .... end of header
- * .... begin body
- * (uint32) properties
- * #properties of the following:
- *     (uint16) property_length, including property_length field itself
+ * -- end of header
+ * -- begin body
+ * (uint32) number of properties
+ * -- repeat for number of properties
+ *     (uint16) property size, including property size field itself
  *     (uint8) type of property
  *     (int8)+ key string, including 0 termination
- *      based on type of property (above), one of:
+ *      based on type of property (given above), one of:
  *       (int32)
  *       (int64)
  *       (double)
  *       (int8)+ for cstring, including 0 termination
  *       (int64, int64) for rate
- * .... end body
- * .... end of item
+ * -- end body
+ * -- end of item
  */
 
+namespace mediametrics {
+
+// Type must match MediaMetrics.java
+enum Type {
+    kTypeNone = 0,
+    kTypeInt32 = 1,
+    kTypeInt64 = 2,
+    kTypeDouble = 3,
+    kTypeCString = 4,
+    kTypeRate = 5,
+};
+
+template<size_t N>
+static inline bool startsWith(const std::string &s, const char (&comp)[N]) {
+    return !strncmp(s.c_str(), comp, N-1);
+}
+
+/**
+ * Media Metrics BaseItem
+ *
+ * A base class which contains utility static functions to write to a byte stream
+ * and access the Media Metrics service.
+ */
+
+class BaseItem {
+    friend class MediaMetricsDeathNotifier; // for dropInstance
+    // enabled 1, disabled 0
+public:
+    // are we collecting analytics data
+    static bool isEnabled();
+
+protected:
+    static constexpr const char * const EnabledProperty = "media.metrics.enabled";
+    static constexpr const char * const EnabledPropertyPersist = "persist.media.metrics.enabled";
+    static const int EnabledProperty_default = 1;
+
+    // let's reuse a binder connection
+    static sp<IMediaAnalyticsService> sAnalyticsService;
+    static sp<IMediaAnalyticsService> getInstance();
+    static void dropInstance();
+    static bool submitBuffer(const char *buffer, size_t len);
+
+    static status_t writeToByteString(
+            const char *name, int32_t value, char **bufferpptr, char *bufferptrmax);
+    static status_t writeToByteString(
+            const char *name, int64_t value, char **bufferpptr, char *bufferptrmax);
+    static status_t writeToByteString(
+            const char *name, double value, char **bufferpptr, char *bufferptrmax);
+    static status_t writeToByteString(
+            const char *name, const std::pair<int64_t, int64_t> &value,
+            char **bufferpptr, char *bufferptrmax);
+    static status_t writeToByteString(
+            const char *name, char * const &value, char **bufferpptr, char *bufferptrmax);
+    static status_t writeToByteString(
+            const char *name, const char * const &value, char **bufferpptr, char *bufferptrmax);
+    struct none_t {}; // for kTypeNone
+    static status_t writeToByteString(
+            const char *name, const none_t &, char **bufferpptr, char *bufferptrmax);
+
+    template<typename T>
+    static status_t sizeOfByteString(const char *name, const T& value) {
+        return 2 + 1 + strlen(name) + 1 + sizeof(value);
+    }
+    template<> // static
+    status_t sizeOfByteString(const char *name, char * const &value) {
+        return 2 + 1 + strlen(name) + 1 + strlen(value) + 1;
+    }
+    template<> // static
+    status_t sizeOfByteString(const char *name, const char * const &value) {
+        return 2 + 1 + strlen(name) + 1 + strlen(value) + 1;
+    }
+    template<> // static
+    status_t sizeOfByteString(const char *name, const none_t &) {
+         return 2 + 1 + strlen(name) + 1;
+    }
+};
+
+/**
+ * Media Metrics BufferedItem
+ *
+ * A base class which represents a put-only Media Metrics item, storing
+ * the Media Metrics data in a buffer with begin and end pointers.
+ *
+ * If a property key is entered twice, it will be stored in the buffer twice,
+ * and (implementation defined) the last value for that key will be used
+ * by the Media Metrics service.
+ *
+ * For realloc, a baseRealloc pointer must be passed in either explicitly
+ * or implicitly in the constructor. This will be updated with the value used on realloc.
+ */
+class BufferedItem : public BaseItem {
+public:
+    static inline constexpr uint16_t kVersion = 0;
+
+    virtual ~BufferedItem() = default;
+    BufferedItem(const BufferedItem&) = delete;
+    BufferedItem& operator=(const BufferedItem&) = delete;
+
+    BufferedItem(const std::string key, char *begin, char *end)
+        : BufferedItem(key.c_str(), begin, end) { }
+
+    BufferedItem(const char *key, char *begin, char *end)
+        : BufferedItem(key, begin, end, nullptr) { }
+
+    BufferedItem(const char *key, char **begin, char *end)
+        : BufferedItem(key, *begin, end, begin) { }
+
+    BufferedItem(const char *key, char *begin, char *end, char **baseRealloc)
+        : mBegin(begin)
+        , mEnd(end)
+        , mBaseRealloc(baseRealloc)
+    {
+        init(key);
+    }
+
+    template<typename T>
+    BufferedItem &set(const char *key, const T& value) {
+        reallocFor(sizeOfByteString(key, value));
+        if (mStatus == NO_ERROR) {
+            mStatus = BaseItem::writeToByteString(key, value, &mBptr, mEnd);
+            ++mPropCount;
+        }
+        return *this;
+    }
+
+    template<typename T>
+    BufferedItem &set(const std::string& key, const T& value) {
+        return set(key.c_str(), value);
+    }
+
+    BufferedItem &setPid(pid_t pid) {
+        if (mStatus == NO_ERROR) {
+            copyTo(mBegin + mHeaderLen - 16, (int32_t)pid);
+        }
+        return *this;
+    }
+
+    BufferedItem &setUid(uid_t uid) {
+        if (mStatus == NO_ERROR) {
+            copyTo(mBegin + mHeaderLen - 12, (int32_t)uid);
+        }
+        return *this;
+    }
+
+    BufferedItem &setTimestamp(nsecs_t timestamp) {
+        if (mStatus == NO_ERROR) {
+            copyTo(mBegin + mHeaderLen - 8, (int64_t)timestamp);
+        }
+        return *this;
+    }
+
+    bool record() {
+        return updateHeader()
+                && BaseItem::submitBuffer(getBuffer(), getLength());
+    }
+
+    bool isValid () const {
+        return mStatus == NO_ERROR;
+    }
+
+    char *getBuffer() const { return mBegin; }
+    size_t getLength() const { return mBptr - mBegin; }
+    size_t getRemaining() const { return mEnd - mBptr; }
+    size_t getCapacity() const { return mEnd - mBegin; }
+
+    bool updateHeader() {
+        if (mStatus != NO_ERROR) return false;
+        copyTo(mBegin + 0, (uint32_t)getLength());
+        copyTo(mBegin + 4, (uint32_t)mHeaderLen);
+        copyTo(mBegin + mHeaderLen, (uint32_t)mPropCount);
+        return true;
+    }
+
+protected:
+    BufferedItem() = default;
+
+    void reallocFor(size_t required) {
+        if (mStatus != NO_ERROR) return;
+        const size_t remaining = getRemaining();
+        if (required <= remaining) return;
+        if (mBaseRealloc == nullptr) {
+            mStatus = NO_MEMORY;
+            return;
+        }
+
+        const size_t current = getLength();
+        size_t minimum = current + required;
+        if (minimum > SSIZE_MAX >> 1) {
+            mStatus = NO_MEMORY;
+            return;
+        }
+        minimum <<= 1;
+        void *newptr = realloc(*mBaseRealloc, minimum);
+        if (newptr == nullptr) {
+            mStatus = NO_MEMORY;
+            return;
+        }
+        if (newptr != *mBaseRealloc) {
+            // ALOGD("base changed! current:%zu new size %zu", current, minimum);
+            if (*mBaseRealloc == nullptr) {
+                memcpy(newptr, mBegin, current);
+            }
+            mBegin = (char *)newptr;
+            *mBaseRealloc = mBegin;
+            mEnd = mBegin + minimum;
+            mBptr = mBegin + current;
+        } else {
+            // ALOGD("base kept! current:%zu new size %zu", current, minimum);
+            mEnd = mBegin + minimum;
+        }
+    }
+    template<typename T>
+    void copyTo(char *ptr, const T& value) {
+        memcpy(ptr, &value, sizeof(value));
+    }
+
+    void init(const char *key) {
+        mBptr = mBegin;
+        const size_t keylen = strlen(key) + 1;
+        mHeaderLen = 4 + 4 + 2 + 2 + keylen + 4 + 4 + 8;
+        reallocFor(mHeaderLen);
+        if (mStatus != NO_ERROR) return;
+        mBptr = mBegin + mHeaderLen + 4; // this includes propcount.
+
+        if (mEnd < mBptr || keylen > UINT16_MAX) {
+           mStatus = NO_MEMORY;
+           mBptr = mEnd;
+           return;
+        }
+        copyTo(mBegin + 8, kVersion);
+        copyTo(mBegin + 10, (uint16_t)keylen);
+        strcpy(mBegin + 12, key);
+
+        // initialize some parameters (that could be overridden)
+        setPid(-1);
+        setUid(-1);
+        setTimestamp(0);
+    }
+
+    char *mBegin = nullptr;
+    char *mEnd = nullptr;
+    char **mBaseRealloc = nullptr;  // set to an address if realloc should be done.
+                                    // upon return, that pointer is updated with
+                                    // whatever needs to be freed.
+    char *mBptr = nullptr;
+    status_t mStatus = NO_ERROR;
+    uint32_t mPropCount = 0;
+    uint32_t mHeaderLen = 0;
+};
+
+/**
+ * MediaMetrics Item is a stack allocated media analytics item used for
+ * fast logging.  It falls over to a malloc if needed.
+ *
+ * This is templated with a buffer size to allocate on the stack.
+ */
+template <size_t N = 4096>
+class Item : public BufferedItem {
+public:
+    explicit Item(const std::string key) : Item(key.c_str()) { }
+
+    // Since this class will not be defined before the base class, we initialize variables
+    // in our own order.
+    explicit Item(const char *key) {
+         mBegin = mBuffer;
+         mEnd = mBuffer + N;
+         mBaseRealloc = &mReallocPtr;
+         init(key);
+    }
+
+    ~Item() override {
+        if (mReallocPtr != nullptr) { // do the check before calling free to avoid overhead.
+            free(mReallocPtr);
+        }
+    }
+
+private:
+    char *mReallocPtr = nullptr;  // set non-null by base class if realloc happened.
+    char mBuffer[N];
+};
+
+} // mediametrics
+
 /**
  * Media Metrics MediaAnalyticsItem
  *
  * A mutable item representing an event or record that will be
- * logged with the Media Metrics service.
+ * logged with the Media Metrics service.  For client logging, one should
+ * use the mediametrics::Item.
  *
+ * The MediaAnalyticsItem is designed for the service as it has getters.
  */
-
-class MediaAnalyticsItem {
+class MediaAnalyticsItem : public mediametrics::BaseItem {
     friend class MediaMetricsJNI;           // TODO: remove this access
-    friend class MediaMetricsDeathNotifier; // for dropInstance
 
 public:
 
+     // TODO: remove this duplicate definition when frameworks base is updated.
             enum Type {
                 kTypeNone = 0,
                 kTypeInt32 = 1,
@@ -281,28 +577,12 @@
     status_t writeToByteString(char **bufferptr, size_t *length) const;
     status_t readFromByteString(const char *bufferptr, size_t length);
 
-    static status_t writeToByteString(
-            const char *name, int32_t value, char **bufferpptr, char *bufferptrmax);
-    static status_t writeToByteString(
-            const char *name, int64_t value, char **bufferpptr, char *bufferptrmax);
-    static status_t writeToByteString(
-            const char *name, double value, char **bufferpptr, char *bufferptrmax);
-    static status_t writeToByteString(
-            const char *name, const std::pair<int64_t, int64_t> &value, char **bufferpptr, char *bufferptrmax);
-    static status_t writeToByteString(
-            const char *name, char * const &value, char **bufferpptr, char *bufferptrmax);
-    struct none_t {}; // for kTypeNone
-    static status_t writeToByteString(
-            const char *name, const none_t &, char **bufferpptr, char *bufferptrmax);
 
         std::string toString() const;
         std::string toString(int version) const;
         const char *toCString();
         const char *toCString(int version);
 
-        // are we collecting analytics data
-        static bool isEnabled();
-
     protected:
 
         // merge fields from arg into this
@@ -316,15 +596,7 @@
     int32_t writeToParcel0(Parcel *) const;
     int32_t readFromParcel0(const Parcel&);
 
-    // enabled 1, disabled 0
-    static constexpr const char * const EnabledProperty = "media.metrics.enabled";
-    static constexpr const char * const EnabledPropertyPersist = "persist.media.metrics.enabled";
-    static const int EnabledProperty_default = 1;
 
-    // let's reuse a binder connection
-    static sp<IMediaAnalyticsService> sAnalyticsService;
-    static sp<IMediaAnalyticsService> getInstance();
-    static void dropInstance();
 
     // checks equality even with nullptr.
     static bool stringEquals(const char *a, const char *b) {
@@ -435,7 +707,29 @@
         }
 
         bool isNamed(const char *name) const {
-            return strcmp(name, mName) == 0;
+            return stringEquals(name, mName);
+        }
+
+        template <typename T> void visit(T f) const {
+            switch (mType) {
+            case MediaAnalyticsItem::kTypeInt32:
+                f(u.int32Value);
+                return;
+            case MediaAnalyticsItem::kTypeInt64:
+                f(u.int64Value);
+                return;
+            case MediaAnalyticsItem::kTypeDouble:
+                f(u.doubleValue);
+                return;
+            case MediaAnalyticsItem::kTypeRate:
+                f(u.rate);
+                return;
+            case MediaAnalyticsItem::kTypeCString:
+                f(u.CStringValue);
+                return;
+            default:
+                return;
+            }
         }
 
         template <typename T> bool get(T *value) const = delete;
diff --git a/services/mediaanalytics/MediaAnalyticsService.cpp b/services/mediaanalytics/MediaAnalyticsService.cpp
index f1e9af7..33a5a90 100644
--- a/services/mediaanalytics/MediaAnalyticsService.cpp
+++ b/services/mediaanalytics/MediaAnalyticsService.cpp
@@ -30,6 +30,8 @@
 
 namespace android {
 
+using namespace mediametrics;
+
 // individual records kept in memory: age or count
 // age: <= 28 hours (1 1/6 days)
 // count: hard limit of # records
diff --git a/services/mediaanalytics/MediaAnalyticsService.h b/services/mediaanalytics/MediaAnalyticsService.h
index eb7d725..7ac14da 100644
--- a/services/mediaanalytics/MediaAnalyticsService.h
+++ b/services/mediaanalytics/MediaAnalyticsService.h
@@ -45,6 +45,12 @@
         return submitInternal(item, false /* release */);
     }
 
+    status_t submitBuffer(const char *buffer, size_t length) override {
+        MediaAnalyticsItem *item = new MediaAnalyticsItem();
+        return item->readFromByteString(buffer, length)
+                ?: submitInternal(item, true /* release */);
+    }
+
     status_t dump(int fd, const Vector<String16>& args) override;
 
     static constexpr const char * const kServiceName = "media.metrics";
diff --git a/services/mediaanalytics/tests/mediametrics_tests.cpp b/services/mediaanalytics/tests/mediametrics_tests.cpp
index 09ca114..fc02767 100644
--- a/services/mediaanalytics/tests/mediametrics_tests.cpp
+++ b/services/mediaanalytics/tests/mediametrics_tests.cpp
@@ -281,3 +281,78 @@
   }
   ASSERT_EQ(31, mask);
 }
+
+TEST(mediametrics_tests, item_expansion) {
+  mediametrics::Item<1> item("I");
+  item.set("i32", (int32_t)1)
+      .set("i64", (int64_t)2)
+      .set("double", (double)3.125)
+      .set("string", "abcdefghijklmnopqrstuvwxyz")
+      .set("rate", std::pair<int64_t, int64_t>(11, 12));
+  ASSERT_TRUE(item.updateHeader());
+
+  MediaAnalyticsItem item2;
+  item2.readFromByteString(item.getBuffer(), item.getLength());
+  ASSERT_EQ((pid_t)-1, item2.getPid());
+  ASSERT_EQ((uid_t)-1, item2.getUid());
+  int mask = 0;
+  for (auto &prop : item2) {
+      const char *name = prop.getName();
+      if (!strcmp(name, "i32")) {
+          int32_t i32;
+          ASSERT_TRUE(prop.get(&i32));
+          ASSERT_EQ(1, i32);
+          mask |= 1;
+      } else if (!strcmp(name, "i64")) {
+          int64_t i64;
+          ASSERT_TRUE(prop.get(&i64));
+          ASSERT_EQ(2, i64);
+          mask |= 2;
+      } else if (!strcmp(name, "double")) {
+          double d;
+          ASSERT_TRUE(prop.get(&d));
+          ASSERT_EQ(3.125, d);
+          mask |= 4;
+      } else if (!strcmp(name, "string")) {
+          const char *s;
+          ASSERT_TRUE(prop.get(&s));
+          ASSERT_EQ(0, strcmp(s, "abcdefghijklmnopqrstuvwxyz"));
+          mask |= 8;
+      } else if (!strcmp(name, "rate")) {
+          std::pair<int64_t, int64_t> r;
+          ASSERT_TRUE(prop.get(&r));
+          ASSERT_EQ(11, r.first);
+          ASSERT_EQ(12, r.second);
+          mask |= 16;
+      } else {
+          FAIL();
+      }
+  }
+  ASSERT_EQ(31, mask);
+}
+
+TEST(mediametrics_tests, item_expansion2) {
+  mediametrics::Item<1> item("Bigly");
+  item.setPid(123)
+      .setUid(456);
+  constexpr size_t count = 10000;
+
+  for (size_t i = 0; i < count; ++i) {
+    // printf("recording %zu, %p, len:%zu of %zu  remaining:%zu \n", i, item.getBuffer(), item.getLength(), item.getCapacity(), item.getRemaining());
+    item.set(std::to_string(i).c_str(), (int32_t)i);
+  }
+  ASSERT_TRUE(item.updateHeader());
+
+  MediaAnalyticsItem item2;
+  printf("begin buffer:%p  length:%zu\n", item.getBuffer(), item.getLength());
+  fflush(stdout);
+  item2.readFromByteString(item.getBuffer(), item.getLength());
+
+  ASSERT_EQ((pid_t)123, item2.getPid());
+  ASSERT_EQ((uid_t)456, item2.getUid());
+  for (size_t i = 0; i < count; ++i) {
+    int32_t i32;
+    ASSERT_TRUE(item2.getInt32(std::to_string(i).c_str(), &i32));
+    ASSERT_EQ((int32_t)i, i32);
+  }
+}