Merge "initial mediasanalytics framework"
diff --git a/include/media/IMediaAnalyticsService.h b/include/media/IMediaAnalyticsService.h
new file mode 100644
index 0000000..21da6ad
--- /dev/null
+++ b/include/media/IMediaAnalyticsService.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ANDROID_IMEDIAANALYTICSSERVICE_H
+#define ANDROID_IMEDIAANALYTICSSERVICE_H
+
+#include <utils/String8.h>
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+
+#include <sys/types.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+#include <utils/List.h>
+
+#include <binder/IServiceManager.h>
+
+#include <media/MediaAnalyticsItem.h>
+// nope...#include <media/MediaAnalytics.h>
+
+namespace android {
+
+class IMediaAnalyticsService: public IInterface
+{
+public:
+    DECLARE_META_INTERFACE(MediaAnalyticsService);
+
+    // generate a unique sessionID to use across multiple requests
+    // 'unique' is within this device, since last reboot
+    virtual MediaAnalyticsItem::SessionID_t generateUniqueSessionID() = 0;
+
+    // submit the indicated record to the mediaanalytics service, where
+    // it will be merged (if appropriate) with incomplete records that
+    // share the same key and sessionid.
+    // 'forcenew' marks any matching incomplete record as complete before
+    // inserting this new record.
+    // returns the sessionID associated with that item.
+    virtual MediaAnalyticsItem::SessionID_t submit(sp<MediaAnalyticsItem> item, bool forcenew) = 0;
+
+
+    // return lists of records that match the supplied parameters.
+    // finished [or not] records since time 'ts' with key 'key'
+    // timestamp 'ts' is nanoseconds, unix time.
+    virtual List<sp<MediaAnalyticsItem>> *getMediaAnalyticsItemList(bool finished, int64_t ts) = 0;
+    virtual List<sp<MediaAnalyticsItem>> *getMediaAnalyticsItemList(bool finished, int64_t ts, MediaAnalyticsItem::Key key) = 0;
+
+};
+
+// ----------------------------------------------------------------------------
+
+class BnMediaAnalyticsService: public BnInterface<IMediaAnalyticsService>
+{
+public:
+    virtual status_t    onTransact( uint32_t code,
+                                    const Parcel& data,
+                                    Parcel* reply,
+                                    uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_IMEDIASTATISTICSSERVICE_H
diff --git a/include/media/MediaAnalyticsItem.h b/include/media/MediaAnalyticsItem.h
new file mode 100644
index 0000000..73c9dd4
--- /dev/null
+++ b/include/media/MediaAnalyticsItem.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ANDROID_MEDIA_MEDIAANALYTICSITEM_H
+#define ANDROID_MEDIA_MEDIAANALYTICSITEM_H
+
+#include <cutils/properties.h>
+#include <sys/types.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+#include <utils/Timers.h>
+
+#include <media/stagefright/foundation/AString.h>
+
+namespace android {
+
+
+
+class IMediaAnalyticsService;
+
+// the class interface
+//
+
+class MediaAnalyticsItem : public RefBase {
+
+    friend class MediaAnalyticsService;
+    friend class IMediaAnalyticsService;
+
+    public:
+
+        // sessionid
+        // unique within device, within boot,
+        typedef int64_t SessionID_t;
+        static constexpr SessionID_t SessionIDInvalid = -1;
+        static constexpr SessionID_t SessionIDNone = 0;
+
+        // Key: the record descriminator
+        // values for the record discriminator
+        // values can be "component/component"
+        // basic values: "video", "audio", "drm"
+        // XXX: need to better define the format
+        typedef AString Key;
+        static const Key kKeyNone;              // ""
+        static const Key kKeyAny;               // "*"
+
+        // Attr: names for attributes within a record
+        // format "prop1" or "prop/subprop"
+        // XXX: need to better define the format
+        typedef AString Attr;
+
+
+    public:
+
+        // access functions for the class
+        MediaAnalyticsItem();
+        MediaAnalyticsItem(Key);
+        ~MediaAnalyticsItem();
+
+        // so clients can send intermediate values to be overlaid later
+        MediaAnalyticsItem &setFinalized(bool);
+        bool getFinalized() const;
+
+        // SessionID ties multiple submissions for same key together
+        // so that if video "height" and "width" are known at one point
+        // and "framerate" is only known later, they can be be brought
+        // together.
+        MediaAnalyticsItem &setSessionID(SessionID_t);
+        MediaAnalyticsItem &clearSessionID();
+        SessionID_t getSessionID() const;
+        // generates and stores a new ID iff mSessionID == SessionIDNone
+        SessionID_t generateSessionID();
+
+        // reset all contents, discarding any extra data
+        void clear();
+
+        // set the key discriminator for the record.
+        // most often initialized as part of the constructor
+        MediaAnalyticsItem &setKey(MediaAnalyticsItem::Key);
+        MediaAnalyticsItem::Key getKey();
+
+        // # of attributes in the record
+        int32_t count() const;
+
+        // set values appropriately
+        // return values tell us whether we overwrote an existing value
+        bool setInt32(Attr, int32_t value);
+        bool setInt64(Attr, int64_t value);
+        bool setDouble(Attr, double value);
+        bool setCString(Attr, const char *value);
+
+        // fused get/add/set; if attr wasn't there, it's a simple set.
+        // type-mismatch counts as "wasn't there".
+        // return value tells us whether we overwrote an existing value
+        bool addInt32(Attr, int32_t value);
+        bool addInt64(Attr, int64_t value);
+        bool addDouble(Attr, double value);
+
+        // find & extract values
+        // return indicates whether attr exists (and thus value filled in)
+        bool getInt32(Attr, int32_t *value);
+        bool getInt64(Attr, int64_t *value);
+        bool getDouble(Attr, double *value);
+        bool getCString(Attr, char **value);
+
+        // parameter indicates whether to close any existing open
+        // record with same key before establishing a new record
+        bool selfrecord(bool);
+        bool selfrecord();
+
+        // remove indicated attributes and their values
+        // filterNot() could also be called keepOnly()
+        // return value is # attributes removed
+        // XXX: perhaps 'remove' instead of 'filter'
+        // XXX: filterNot would become 'keep'
+        int32_t filter(int count, Attr attrs[]);
+        int32_t filterNot(int count, Attr attrs[]);
+        int32_t filter(Attr attr);
+
+        // below here are used on server side or to talk to server
+        // clients need not worry about these.
+
+        // timestamp, pid, and uid only used on server side
+	// timestamp is in 'nanoseconds, unix time'
+        MediaAnalyticsItem &setTimestamp(nsecs_t);
+        nsecs_t getTimestamp() const;
+
+        MediaAnalyticsItem &setPid(pid_t);
+        pid_t getPid() const;
+
+        MediaAnalyticsItem &setUid(uid_t);
+        uid_t getUid() const;
+
+        // our serialization code for binder calls
+        int32_t writeToParcel(Parcel *);
+        int32_t readFromParcel(const Parcel&);
+
+        AString toString();
+
+        // are we collecting analytics data
+        static bool isEnabled();
+
+    protected:
+
+        // merge fields from arg into this
+        // with rules for first/last/add, etc
+        // XXX: document semantics and how they are indicated
+        bool merge(sp<MediaAnalyticsItem> );
+
+        // enabled 1, disabled 0
+        static const char * const EnabledProperty;
+        static const char * const EnabledPropertyPersist;
+        static const int   EnabledProperty_default;
+
+    private:
+
+        // to help validate that A doesn't mess with B's records
+        pid_t     mPid;
+        uid_t     mUid;
+
+        // let's reuse a binder connection
+        static sp<IMediaAnalyticsService> sAnalyticsService;
+        static sp<IMediaAnalyticsService> getInstance();
+
+        // tracking information
+        SessionID_t mSessionID;         // grouping similar records
+        nsecs_t mTimestamp;             // ns, system_time_monotonic
+
+        // will this record accept further updates
+        bool mFinalized;
+
+        Key mKey;
+
+        class Item : public RefBase {
+
+         public:
+
+            enum Type {
+                kTypeNone = 0,
+                kTypeInt32 = 1,
+                kTypeInt64 = 2,
+                kTypeDouble = 3,
+                kTypeCString = 4,
+            };
+
+            Item();
+            ~Item();
+            void clear();
+
+            Type mType;
+            union {
+                    int32_t int32Value;
+                    int64_t int64Value;
+                    double doubleValue;
+                    char *CStringValue;
+            } u;
+        };
+        KeyedVector<Attr, sp<Item>> mItems;
+
+};
+
+} // namespace android
+
+#endif
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index 2249222..02947b0 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -26,6 +26,7 @@
     IMediaPlayer.cpp \
     IMediaRecorder.cpp \
     IMediaSource.cpp \
+    IMediaAnalyticsService.cpp \
     IRemoteDisplay.cpp \
     IRemoteDisplayClient.cpp \
     IResourceManagerClient.cpp \
@@ -34,6 +35,7 @@
     MediaCodecBuffer.cpp \
     MediaCodecInfo.cpp \
     MediaDefs.cpp \
+    MediaAnalyticsItem.cpp \
     MediaUtils.cpp \
     Metadata.cpp \
     mediarecorder.cpp \
diff --git a/media/libmedia/IMediaAnalyticsService.cpp b/media/libmedia/IMediaAnalyticsService.cpp
new file mode 100644
index 0000000..afe9c36
--- /dev/null
+++ b/media/libmedia/IMediaAnalyticsService.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#define LOG_TAG "MediaAnalytics"
+
+#include <stdint.h>
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include <binder/Parcel.h>
+#include <binder/IMemory.h>
+#include <binder/IPCThreadState.h>
+#include <media/IHDCP.h>
+#include <media/IMediaCodecList.h>
+#include <media/IMediaHTTPService.h>
+#include <media/IMediaPlayerService.h>
+#include <media/IMediaRecorder.h>
+#include <media/IOMX.h>
+#include <media/IRemoteDisplay.h>
+#include <media/IRemoteDisplayClient.h>
+#include <media/IStreamSource.h>
+
+#include <utils/Errors.h>  // for status_t
+#include <utils/List.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include <media/MediaAnalyticsItem.h>
+#include <media/IMediaAnalyticsService.h>
+
+#define DEBUGGING               0
+#define DEBUGGING_FLOW          0
+#define DEBUGGING_RETURNS       0
+
+namespace android {
+
+enum {
+    GENERATE_UNIQUE_SESSIONID = IBinder::FIRST_CALL_TRANSACTION,
+    SUBMIT_ITEM,
+    GET_ITEM_LIST,
+};
+
+class BpMediaAnalyticsService: public BpInterface<IMediaAnalyticsService>
+{
+public:
+    explicit BpMediaAnalyticsService(const sp<IBinder>& impl)
+        : BpInterface<IMediaAnalyticsService>(impl)
+    {
+    }
+
+    virtual MediaAnalyticsItem::SessionID_t generateUniqueSessionID() {
+        Parcel data, reply;
+        status_t err;
+        MediaAnalyticsItem::SessionID_t sessionid =
+                        MediaAnalyticsItem::SessionIDInvalid;
+
+        data.writeInterfaceToken(IMediaAnalyticsService::getInterfaceDescriptor());
+        err = remote()->transact(GENERATE_UNIQUE_SESSIONID, data, &reply);
+        if (err != NO_ERROR) {
+            ALOGW("bad response from service");
+            return MediaAnalyticsItem::SessionIDInvalid;
+        }
+        sessionid = reply.readInt64();
+        if (DEBUGGING_RETURNS) {
+            ALOGD("the caller gets a sessionid of %" PRId64 " back", sessionid);
+        }
+        return sessionid;
+    }
+
+    virtual MediaAnalyticsItem::SessionID_t submit(sp<MediaAnalyticsItem> item, bool forcenew)
+    {
+        // have this record submit itself
+        // this will be a binder call with appropriate timing
+        // return value is the uuid that the system generated for it.
+        // the return value 0 and -1 are reserved.
+        // -1 to indicate that there was a problem recording...
+
+        Parcel data, reply;
+        status_t err;
+
+        if (item == NULL) {
+                return MediaAnalyticsItem::SessionIDInvalid;
+        }
+
+        data.writeInterfaceToken(IMediaAnalyticsService::getInterfaceDescriptor());
+        if(DEBUGGING_FLOW) {
+            ALOGD("client offers record: %s", item->toString().c_str());
+        }
+        data.writeBool(forcenew);
+        item->writeToParcel(&data);
+
+        err = remote()->transact(SUBMIT_ITEM, data, &reply);
+        if (err != NO_ERROR) {
+            return MediaAnalyticsItem::SessionIDInvalid;
+        }
+
+        // get an answer out of 'reply'
+        int64_t sessionid = reply.readInt64();
+        if (DEBUGGING_RETURNS) {
+            ALOGD("the caller gets sessionid=%" PRId64 "", sessionid);
+        }
+        return sessionid;
+    }
+
+    virtual List<sp<MediaAnalyticsItem>> *getMediaAnalyticsItemList(bool finished, nsecs_t ts)
+    {
+            return getMediaAnalyticsItemList(finished, ts, MediaAnalyticsItem::kKeyAny);
+    }
+
+    virtual List<sp<MediaAnalyticsItem>> *getMediaAnalyticsItemList(bool finished, nsecs_t ts, MediaAnalyticsItem::Key key)
+    {
+        Parcel data, reply;
+        status_t err;
+
+        data.writeInterfaceToken(IMediaAnalyticsService::getInterfaceDescriptor());
+        data.writeInt32(finished);
+        data.writeInt64(ts);
+        const char *str = key.c_str();
+        if (key.empty()) {
+            str = MediaAnalyticsItem::kKeyNone.c_str();
+        }
+        data.writeCString(str);
+        err = remote()->transact(GET_ITEM_LIST, data, &reply);
+	if (err != NO_ERROR) {
+	    return NULL;
+	}
+
+        // read a count
+        int32_t count = reply.readInt32();
+        List<sp<MediaAnalyticsItem>> *list = NULL;
+
+        if (count > 0) {
+            list = new List<sp<MediaAnalyticsItem>>();
+            for (int i=0;i<count;i++) {
+                sp<MediaAnalyticsItem> item = new MediaAnalyticsItem;
+                // XXX: watch for failures here
+                item->readFromParcel(reply);
+                list->push_back(item);
+            }
+        }
+
+        return list;
+    }
+};
+
+IMPLEMENT_META_INTERFACE(MediaAnalyticsService, "android.media.IMediaAnalyticsService");
+
+// ----------------------------------------------------------------------
+
+status_t BnMediaAnalyticsService::onTransact(
+    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+
+
+    // get calling pid/tid
+    IPCThreadState *ipc = IPCThreadState::self();
+    int clientPid = ipc->getCallingPid();
+    // permission checking
+
+    if(DEBUGGING_FLOW) {
+        ALOGD("running in service, code %d, pid %d; called from pid %d",
+            code, getpid(), clientPid);
+    }
+
+    switch (code) {
+
+        case GENERATE_UNIQUE_SESSIONID: {
+            CHECK_INTERFACE(IMediaAnalyticsService, data, reply);
+
+            MediaAnalyticsItem::SessionID_t sessionid = generateUniqueSessionID();
+            reply->writeInt64(sessionid);
+
+            return NO_ERROR;
+        } break;
+
+        case SUBMIT_ITEM: {
+            CHECK_INTERFACE(IMediaAnalyticsService, data, reply);
+
+            bool forcenew;
+            sp<MediaAnalyticsItem> item = new MediaAnalyticsItem;
+
+            data.readBool(&forcenew);
+            item->readFromParcel(data);
+
+            item->setPid(clientPid);
+
+	    // submit() takes ownership of / responsibility for the item
+            MediaAnalyticsItem::SessionID_t sessionid = submit(item, forcenew);
+            reply->writeInt64(sessionid);
+
+            return NO_ERROR;
+        } break;
+
+        case GET_ITEM_LIST: {
+            CHECK_INTERFACE(IMediaPlayerService, data, reply);
+            // get the parameters
+            bool finished = data.readInt32();
+            nsecs_t ts = data.readInt64();
+            MediaAnalyticsItem::Key key = data.readCString();
+
+            // find the (0 or more) items
+            List<sp<MediaAnalyticsItem>> *list =  getMediaAnalyticsItemList(finished, ts, key);
+            // encapsulate/serialize them
+            reply->writeInt32(list->size());
+            if (list->size() > 0) {
+                    for (List<sp<MediaAnalyticsItem>>::iterator it = list->begin();
+                         it != list->end(); it++) {
+                            (*it)->writeToParcel(reply);
+                    }
+
+
+            }
+
+            // avoid leakiness; organized discarding of list and its contents
+            list->clear();
+            delete list;
+
+            return NO_ERROR;
+        } break;
+
+        default:
+            return BBinder::onTransact(code, data, reply, flags);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+} // namespace android
diff --git a/media/libmedia/MediaAnalyticsItem.cpp b/media/libmedia/MediaAnalyticsItem.cpp
new file mode 100644
index 0000000..5f05d5a
--- /dev/null
+++ b/media/libmedia/MediaAnalyticsItem.cpp
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "MediaAnalyticsItem"
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <binder/Parcel.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <utils/Mutex.h>
+#include <utils/RefBase.h>
+#include <utils/SortedVector.h>
+#include <utils/threads.h>
+
+#include <media/stagefright/foundation/AString.h>
+
+#include <binder/IServiceManager.h>
+#include <media/IMediaAnalyticsService.h>
+#include <media/MediaAnalyticsItem.h>
+
+namespace android {
+
+#define DEBUG_SERVICEACCESS     0
+
+// the few universal keys we have
+const MediaAnalyticsItem::Key MediaAnalyticsItem::kKeyAny  = "any";
+const MediaAnalyticsItem::Key MediaAnalyticsItem::kKeyNone  = "none";
+
+const char * const MediaAnalyticsItem::EnabledProperty  = "media.analytics.enabled";
+const char * const MediaAnalyticsItem::EnabledPropertyPersist  = "persist.media.analytics.enabled";
+const int MediaAnalyticsItem::EnabledProperty_default  = 0;
+
+
+// access functions for the class
+MediaAnalyticsItem::MediaAnalyticsItem()
+    : RefBase(),
+      mPid(0),
+      mUid(0),
+      mSessionID(MediaAnalyticsItem::SessionIDNone),
+      mTimestamp(0),
+      mFinalized(0) {
+    mKey = MediaAnalyticsItem::kKeyNone;
+}
+
+MediaAnalyticsItem::MediaAnalyticsItem(MediaAnalyticsItem::Key key)
+    : RefBase(),
+      mPid(0),
+      mUid(0),
+      mSessionID(MediaAnalyticsItem::SessionIDNone),
+      mTimestamp(0),
+      mFinalized(0) {
+    mKey = key;
+}
+
+MediaAnalyticsItem::~MediaAnalyticsItem() {
+    clear();
+}
+
+// so clients can send intermediate values to be overlaid later
+MediaAnalyticsItem &MediaAnalyticsItem::setFinalized(bool value) {
+    mFinalized = value;
+    return *this;
+}
+
+bool MediaAnalyticsItem::getFinalized() const {
+    return mFinalized;
+}
+
+MediaAnalyticsItem &MediaAnalyticsItem::setSessionID(MediaAnalyticsItem::SessionID_t id) {
+    mSessionID = id;
+    return *this;
+}
+
+MediaAnalyticsItem::SessionID_t MediaAnalyticsItem::getSessionID() const {
+    return mSessionID;
+}
+
+MediaAnalyticsItem::SessionID_t MediaAnalyticsItem::generateSessionID() {
+    MediaAnalyticsItem::SessionID_t newid = SessionIDNone;
+    ALOGD("generateSessionID()");
+
+    if (mSessionID == SessionIDNone) {
+        // get one from the server
+        sp<IMediaAnalyticsService> svc = getInstance();
+        if (svc != NULL) {
+            newid = svc->generateUniqueSessionID();
+        }
+        mSessionID = newid;
+    }
+
+    return mSessionID;
+}
+
+MediaAnalyticsItem &MediaAnalyticsItem::clearSessionID() {
+    mSessionID = MediaAnalyticsItem::SessionIDNone;
+    return *this;
+}
+
+MediaAnalyticsItem &MediaAnalyticsItem::setTimestamp(nsecs_t ts) {
+    mTimestamp = ts;
+    return *this;
+}
+
+nsecs_t MediaAnalyticsItem::getTimestamp() const {
+    return mTimestamp;
+}
+
+MediaAnalyticsItem &MediaAnalyticsItem::setPid(pid_t pid) {
+    mPid = pid;
+    return *this;
+}
+
+pid_t MediaAnalyticsItem::getPid() const {
+    return mPid;
+}
+
+MediaAnalyticsItem &MediaAnalyticsItem::setUid(uid_t uid) {
+    mUid = uid;
+    return *this;
+}
+
+uid_t MediaAnalyticsItem::getUid() const {
+    return mUid;
+}
+
+void MediaAnalyticsItem::clear() {
+
+    mKey.clear();
+
+#if 0
+    // not sure that I need to (or should) be doing this...
+    // seeing some strangeness in some records
+    int count = mItems.size();
+    for (int i = 0 ; i < count; i++ ) {
+        MediaAnalyticsItem::Attr attr = mItems.keyAt(i);
+        const sp<Item> value = mItems.valueAt(i);
+        value->clear();
+        attr.clear();
+    }
+    mItems.clear();
+#endif
+
+    return;
+}
+
+// this key is for the overall record -- "vid" or "aud"
+// assuming for the moment we use int32_t like the
+// media frameworks MetaData.cpp
+MediaAnalyticsItem &MediaAnalyticsItem::setKey(MediaAnalyticsItem::Key key) {
+    // XXX: possible validation of legal keys.
+    mKey = key;
+    return *this;
+}
+
+MediaAnalyticsItem::Key MediaAnalyticsItem::getKey() {
+    return mKey;
+}
+
+// number of keys we have in our dictionary
+// we won't upload empty records
+int32_t MediaAnalyticsItem::count() const {
+    return mItems.size();
+}
+
+// set the values
+bool MediaAnalyticsItem::setInt32(MediaAnalyticsItem::Attr attr, int32_t value) {
+    ssize_t i = mItems.indexOfKey(attr);
+    bool overwrote = true;
+    if (i<0) {
+        sp<Item> item = new Item();
+        i = mItems.add(attr, item);
+        overwrote = false;
+    }
+    sp<Item> &item = mItems.editValueAt(i);
+    item->mType = MediaAnalyticsItem::Item::kTypeInt32;
+    item->u.int32Value = value;
+    return overwrote;
+}
+
+bool MediaAnalyticsItem::setInt64(MediaAnalyticsItem::Attr attr, int64_t value) {
+    ssize_t i = mItems.indexOfKey(attr);
+    bool overwrote = true;
+    if (i<0) {
+        sp<Item> item = new Item();
+        i = mItems.add(attr, item);
+        overwrote = false;
+    }
+    sp<Item> &item = mItems.editValueAt(i);
+    item->mType = MediaAnalyticsItem::Item::kTypeInt64;
+    item->u.int64Value = value;
+    return overwrote;
+}
+
+bool MediaAnalyticsItem::setDouble(MediaAnalyticsItem::Attr attr, double value) {
+    ssize_t i = mItems.indexOfKey(attr);
+    bool overwrote = true;
+    if (i<0) {
+        sp<Item> item = new Item();
+        i = mItems.add(attr, item);
+        overwrote = false;
+    }
+    sp<Item> &item = mItems.editValueAt(i);
+    item->mType = MediaAnalyticsItem::Item::kTypeDouble;
+    item->u.doubleValue = value;
+    return overwrote;
+}
+
+bool MediaAnalyticsItem::setCString(MediaAnalyticsItem::Attr attr, const char *value) {
+    bool overwrote = true;
+    if (value == NULL) return false;
+    // we store our own copy of the supplied string
+    char *nvalue = strdup(value);
+    if (nvalue == NULL) {
+            return false;
+    }
+    ssize_t i = mItems.indexOfKey(attr);
+    if (i<0) {
+        sp<Item> item = new Item();
+        i = mItems.add(attr, item);
+        overwrote = false;
+    }
+    sp<Item> &item = mItems.editValueAt(i);
+    if (item->mType == MediaAnalyticsItem::Item::kTypeCString
+        && item->u.CStringValue != NULL) {
+            free(item->u.CStringValue);
+            item->u.CStringValue = NULL;
+    }
+    item->mType = MediaAnalyticsItem::Item::kTypeCString;
+    item->u.CStringValue = nvalue;
+    return true;
+}
+
+// find/add/set fused into a single operation
+bool MediaAnalyticsItem::addInt32(MediaAnalyticsItem::Attr attr, int32_t value) {
+    ssize_t i = mItems.indexOfKey(attr);
+    bool overwrote = true;
+    if (i<0) {
+        sp<Item> item = new Item();
+        i = mItems.add(attr, item);
+        overwrote = false;
+    }
+    sp<Item> &item = mItems.editValueAt(i);
+    if (overwrote
+        && item->mType == MediaAnalyticsItem::Item::kTypeInt32) {
+        item->u.int32Value += value;
+    } else {
+        // start clean if there was a type mismatch
+        item->u.int32Value = value;
+    }
+    item->mType = MediaAnalyticsItem::Item::kTypeInt32;
+    return overwrote;
+}
+
+bool MediaAnalyticsItem::addInt64(MediaAnalyticsItem::Attr attr, int64_t value) {
+    ssize_t i = mItems.indexOfKey(attr);
+    bool overwrote = true;
+    if (i<0) {
+        sp<Item> item = new Item();
+        i = mItems.add(attr, item);
+        overwrote = false;
+    }
+    sp<Item> &item = mItems.editValueAt(i);
+    if (overwrote
+        && item->mType == MediaAnalyticsItem::Item::kTypeInt64) {
+        item->u.int64Value += value;
+    } else {
+        // start clean if there was a type mismatch
+        item->u.int64Value = value;
+    }
+    item->mType = MediaAnalyticsItem::Item::kTypeInt64;
+    return overwrote;
+}
+
+bool MediaAnalyticsItem::addDouble(MediaAnalyticsItem::Attr attr, double value) {
+    ssize_t i = mItems.indexOfKey(attr);
+    bool overwrote = true;
+    if (i<0) {
+        sp<Item> item = new Item();
+        i = mItems.add(attr, item);
+        overwrote = false;
+    }
+    sp<Item> &item = mItems.editValueAt(i);
+    if (overwrote
+        && item->mType == MediaAnalyticsItem::Item::kTypeDouble) {
+        item->u.doubleValue += value;
+    } else {
+        // start clean if there was a type mismatch
+        item->u.doubleValue = value;
+    }
+    item->mType = MediaAnalyticsItem::Item::kTypeDouble;
+    return overwrote;
+}
+
+// find & extract values
+bool MediaAnalyticsItem::getInt32(MediaAnalyticsItem::Attr attr, int32_t *value) {
+    ssize_t i = mItems.indexOfKey(attr);
+    if (i < 0) {
+        return false;
+    }
+    sp<Item> &item = mItems.editValueAt(i);
+    *value = item->u.int32Value;
+    return true;
+}
+bool MediaAnalyticsItem::getInt64(MediaAnalyticsItem::Attr attr, int64_t *value) {
+    ssize_t i = mItems.indexOfKey(attr);
+    if (i < 0) {
+        return false;
+    }
+    sp<Item> &item = mItems.editValueAt(i);
+    *value = item->u.int64Value;
+    return true;
+}
+bool MediaAnalyticsItem::getDouble(MediaAnalyticsItem::Attr attr, double *value) {
+    ssize_t i = mItems.indexOfKey(attr);
+    if (i < 0) {
+        return false;
+    }
+    sp<Item> &item = mItems.editValueAt(i);
+    *value = item->u.doubleValue;
+    return true;
+}
+
+// caller responsible for the returned string
+bool MediaAnalyticsItem::getCString(MediaAnalyticsItem::Attr attr, char **value) {
+    ssize_t i = mItems.indexOfKey(attr);
+    if (i < 0) {
+        return false;
+    }
+    sp<Item> &item = mItems.editValueAt(i);
+    char *p = strdup(item->u.CStringValue);
+    *value = p;
+    return true;
+}
+
+// remove indicated keys and their values
+// return value is # keys removed
+int32_t MediaAnalyticsItem::filter(int n, MediaAnalyticsItem::Attr attrs[]) {
+    int zapped = 0;
+    if (attrs == NULL) {
+        return -1;
+    }
+    if (n <= 0) {
+        return -1;
+    }
+    for (ssize_t i = 0 ; i < n ;  i++) {
+        ssize_t j = mItems.indexOfKey(attrs[i]);
+        if (j >= 0) {
+            mItems.removeItemsAt(j);
+            zapped++;
+        }
+    }
+    return zapped;
+}
+
+// remove any keys NOT in the provided list
+// return value is # keys removed
+int32_t MediaAnalyticsItem::filterNot(int n, MediaAnalyticsItem::Attr attrs[]) {
+    int zapped = 0;
+    if (attrs == NULL) {
+        return -1;
+    }
+    if (n <= 0) {
+        return -1;
+    }
+    for (ssize_t i = mItems.size()-1 ; i >=0 ;  i--) {
+        const MediaAnalyticsItem::Attr& lattr = mItems.keyAt(i);
+        ssize_t j;
+        for (j= 0; j < n ; j++) {
+            if (lattr == attrs[j]) {
+                mItems.removeItemsAt(i);
+                zapped++;
+                break;
+            }
+        }
+    }
+    return zapped;
+}
+
+// remove a single key
+// return value is 0 (not found) or 1 (found and removed)
+int32_t MediaAnalyticsItem::filter(MediaAnalyticsItem::Attr attr) {
+    if (attr == 0) return -1;
+    ssize_t i = mItems.indexOfKey(attr);
+    if (i < 0) {
+        return 0;
+    }
+    mItems.removeItemsAt(i);
+    return 1;
+}
+
+
+// handle individual items/properties stored within the class
+//
+MediaAnalyticsItem::Item::Item()
+        : mType(kTypeNone)
+{
+}
+
+MediaAnalyticsItem::Item::~Item()
+{
+    clear();
+}
+
+void MediaAnalyticsItem::Item::clear()
+{
+    if (mType == kTypeCString && u.CStringValue != NULL) {
+        free(u.CStringValue);
+        u.CStringValue = NULL;
+    }
+    mType = kTypeNone;
+}
+
+// Parcel / serialize things for binder calls
+//
+
+int32_t MediaAnalyticsItem::readFromParcel(const Parcel& data) {
+    // into 'this' object
+    // .. we make a copy of the string to put away.
+    mKey = data.readCString();
+    mSessionID = data.readInt64();
+    mFinalized = data.readInt32();
+    mTimestamp = data.readInt64();
+
+    int count = data.readInt32();
+    for (int i = 0; i < count ; i++) {
+            MediaAnalyticsItem::Attr attr = data.readCString();
+            int32_t ztype = data.readInt32();
+                switch (ztype) {
+                    case MediaAnalyticsItem::Item::kTypeInt32:
+                            setInt32(attr, data.readInt32());
+                            break;
+                    case MediaAnalyticsItem::Item::kTypeInt64:
+                            setInt64(attr, data.readInt64());
+                            break;
+                    case MediaAnalyticsItem::Item::kTypeDouble:
+                            setDouble(attr, data.readDouble());
+                            break;
+                    case MediaAnalyticsItem::Item::kTypeCString:
+                            setCString(attr, data.readCString());
+                            break;
+                    default:
+                            ALOGE("reading bad item type: %d, idx %d",
+                                  ztype, i);
+                            return -1;
+                }
+    }
+
+    return 0;
+}
+
+int32_t MediaAnalyticsItem::writeToParcel(Parcel *data) {
+    if (data == NULL) return -1;
+
+
+    data->writeCString(mKey.c_str());
+    data->writeInt64(mSessionID);
+    data->writeInt32(mFinalized);
+    data->writeInt64(mTimestamp);
+
+    // set of items
+    int count = mItems.size();
+    data->writeInt32(count);
+    for (int i = 0 ; i < count; i++ ) {
+            MediaAnalyticsItem::Attr attr = mItems.keyAt(i);
+            sp<Item> value = mItems.valueAt(i);
+            {
+                data->writeCString(attr.c_str());
+                data->writeInt32(value->mType);
+                switch (value->mType) {
+                    case MediaAnalyticsItem::Item::kTypeInt32:
+                            data->writeInt32(value->u.int32Value);
+                            break;
+                    case MediaAnalyticsItem::Item::kTypeInt64:
+                            data->writeInt64(value->u.int64Value);
+                            break;
+                    case MediaAnalyticsItem::Item::kTypeDouble:
+                            data->writeDouble(value->u.doubleValue);
+                            break;
+                    case MediaAnalyticsItem::Item::kTypeCString:
+                            data->writeCString(value->u.CStringValue);
+                            break;
+                    default:
+                            ALOGE("found bad item type: %d, idx %d",
+                                  value->mType, i);
+                            break;
+                }
+            }
+    }
+
+    return 0;
+}
+
+
+
+AString MediaAnalyticsItem::toString() {
+
+    AString result = "(";
+    char buffer[256];
+
+    // 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(":");
+    snprintf(buffer, sizeof(buffer), "%" PRId64 ":", mSessionID);
+    result.append(buffer);
+
+    // we need these internally, but don't want to upload them
+    snprintf(buffer, sizeof(buffer), "%d:%d", mUid, mPid);
+    result.append(buffer);
+
+    snprintf(buffer, sizeof(buffer), "%d:", mFinalized);
+    result.append(buffer);
+    snprintf(buffer, sizeof(buffer), "%" PRId64 ":", mTimestamp);
+    result.append(buffer);
+
+    // set of items
+    int count = mItems.size();
+    snprintf(buffer, sizeof(buffer), "%d:", count);
+    result.append(buffer);
+    for (int i = 0 ; i < count; i++ ) {
+            const MediaAnalyticsItem::Attr attr = mItems.keyAt(i);
+            const sp<Item> value = mItems.valueAt(i);
+            switch (value->mType) {
+                case MediaAnalyticsItem::Item::kTypeInt32:
+                        snprintf(buffer,sizeof(buffer),
+                        "%s=%d:", attr.c_str(), value->u.int32Value);
+                        break;
+                case MediaAnalyticsItem::Item::kTypeInt64:
+                        snprintf(buffer,sizeof(buffer),
+                        "%s=%" PRId64 ":", attr.c_str(), value->u.int64Value);
+                        break;
+                case MediaAnalyticsItem::Item::kTypeDouble:
+                        snprintf(buffer,sizeof(buffer),
+                        "%s=%e:", attr.c_str(), value->u.doubleValue);
+                        break;
+                case MediaAnalyticsItem::Item::kTypeCString:
+                        // XXX: worry about escape chars
+                        // XXX: worry about overflowing buffer
+                        snprintf(buffer,sizeof(buffer), "%s=", attr.c_str());
+                        result.append(buffer);
+                        result.append(value->u.CStringValue);
+                        buffer[0] = ':';
+                        buffer[1] = '\0';
+                        break;
+                default:
+                        ALOGE("to_String bad item type: %d",
+                              value->mType);
+                        break;
+            }
+            result.append(buffer);
+    }
+
+    result.append(")");
+
+    return result;
+}
+
+// for the lazy, we offer methods that finds the service and
+// calls the appropriate daemon
+bool MediaAnalyticsItem::selfrecord() {
+    return selfrecord(false);
+}
+
+bool MediaAnalyticsItem::selfrecord(bool forcenew) {
+
+    AString p = this->toString();
+    ALOGD("selfrecord of: %s [forcenew=%d]", p.c_str(), forcenew);
+
+    sp<IMediaAnalyticsService> svc = getInstance();
+
+    if (svc != NULL) {
+        svc->submit(this, forcenew);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+// get a connection we can reuse for most of our lifetime
+// static
+sp<IMediaAnalyticsService> MediaAnalyticsItem::sAnalyticsService;
+static Mutex sInitMutex;
+
+//static
+bool MediaAnalyticsItem::isEnabled() {
+    int enabled = property_get_int32(MediaAnalyticsItem::EnabledProperty, -1);
+
+    if (enabled == -1) {
+        enabled = property_get_int32(MediaAnalyticsItem::EnabledPropertyPersist, -1);
+    }
+    if (enabled == -1) {
+        enabled = MediaAnalyticsItem::EnabledProperty_default;
+    }
+    if (enabled <= 0) {
+        return false;
+    }
+    return true;
+}
+
+//static
+sp<IMediaAnalyticsService> MediaAnalyticsItem::getInstance() {
+    static const char *servicename = "media.analytics";
+    int enabled = isEnabled();
+
+    if (enabled == false) {
+        if (DEBUG_SERVICEACCESS) {
+                ALOGD("disabled");
+        }
+        return NULL;
+    }
+
+    {
+        Mutex::Autolock _l(sInitMutex);
+        const char *badness = "";
+
+
+        if (sAnalyticsService == NULL) {
+            sp<IServiceManager> sm = defaultServiceManager();
+            if (sm != NULL) {
+                sp<IBinder> binder = sm->getService(String16(servicename));
+                if (binder != NULL) {
+                    sAnalyticsService = interface_cast<IMediaAnalyticsService>(binder);
+                } else {
+                    badness = "did not find service";
+                }
+            } else {
+                badness = "No Service Manager access";
+            }
+            // always
+            if (1 || DEBUG_SERVICEACCESS) {
+                if (sAnalyticsService == NULL) {
+                    ALOGD("Unable to bind to service %s: %s", servicename, badness);
+                }
+            }
+        }
+        return sAnalyticsService;
+    }
+}
+
+
+// merge the info from 'incoming' into this record.
+// we finish with a union of this+incoming and special handling for collisions
+bool MediaAnalyticsItem::merge(sp<MediaAnalyticsItem> incoming) {
+
+    // if I don't have key or session id, take them from incoming
+    // 'this' should never be missing both of them...
+    if (mKey.empty()) {
+        mKey = incoming->mKey;
+    } else if (mSessionID == 0) {
+        mSessionID = incoming->mSessionID;
+    }
+
+    // we always take the more recent 'finalized' value
+    setFinalized(incoming->getFinalized());
+
+    // for each attribute from 'incoming', resolve appropriately
+    int nattr = incoming->mItems.size();
+    for (int i = 0 ; i < nattr; i++ ) {
+        const MediaAnalyticsItem::Attr attr = incoming->mItems.keyAt(i);
+        const sp<Item> value = incoming->mItems.valueAt(i);
+
+        const char *p = attr.c_str();
+        char semantic = p[strlen(p)-1];
+
+        switch (semantic) {
+            default:        // default operation is keep new
+            case '>':       // last aka keep new
+                mItems.replaceValueFor(attr, value);
+                break;
+
+            case '<':       /* first aka keep first*/
+                /* nop */
+                break;
+
+            case '+':       /* sum */
+                // XXX validate numeric types, sum in place
+                break;
+
+        }
+    }
+
+    // not sure when we'd return false...
+    return true;
+}
+
+} // namespace android
+
diff --git a/media/libmediaanalyticsservice/Android.mk b/media/libmediaanalyticsservice/Android.mk
new file mode 100644
index 0000000..dd59651
--- /dev/null
+++ b/media/libmediaanalyticsservice/Android.mk
@@ -0,0 +1,44 @@
+LOCAL_PATH:= $(call my-dir)
+
+#
+# libmediaanalyticsservice
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=               \
+    MediaAnalyticsService.cpp      \
+
+LOCAL_SHARED_LIBRARIES :=       \
+    libbinder                   \
+    libcutils                   \
+    liblog                      \
+    libdl                       \
+    libgui                      \
+    libmedia                    \
+    libmediautils               \
+    libstagefright_foundation   \
+    libutils
+
+LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := libmedia
+
+LOCAL_C_INCLUDES :=                                                 \
+    $(TOP)/frameworks/av/media/libstagefright/include               \
+    $(TOP)/frameworks/av/media/libstagefright/rtsp                  \
+    $(TOP)/frameworks/av/media/libstagefright/wifi-display          \
+    $(TOP)/frameworks/av/media/libstagefright/webm                  \
+    $(TOP)/frameworks/av/include/media                              \
+    $(TOP)/frameworks/av/include/camera                             \
+    $(TOP)/frameworks/native/include/media/openmax                  \
+    $(TOP)/frameworks/native/include/media/hardware                 \
+    $(TOP)/external/tremolo/Tremolo                                 \
+    libcore/include                                                 \
+
+LOCAL_CFLAGS += -Werror -Wno-error=deprecated-declarations -Wall
+LOCAL_CLANG := true
+
+LOCAL_MODULE:= libmediaanalyticsservice
+
+include $(BUILD_SHARED_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/media/libmediaanalyticsservice/MediaAnalyticsService.cpp b/media/libmediaanalyticsservice/MediaAnalyticsService.cpp
new file mode 100644
index 0000000..a039c6c
--- /dev/null
+++ b/media/libmediaanalyticsservice/MediaAnalyticsService.cpp
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+// Proxy for media player implementations
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaAnalyticsService"
+#include <utils/Log.h>
+
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <unistd.h>
+
+#include <string.h>
+
+#include <cutils/atomic.h>
+#include <cutils/properties.h> // for property_get
+
+#include <utils/misc.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/MemoryHeapBase.h>
+#include <binder/MemoryBase.h>
+#include <gui/Surface.h>
+#include <utils/Errors.h>  // for status_t
+#include <utils/List.h>
+#include <utils/String8.h>
+#include <utils/SystemClock.h>
+#include <utils/Timers.h>
+#include <utils/Vector.h>
+
+#include <media/AudioPolicyHelper.h>
+#include <media/IMediaHTTPService.h>
+#include <media/IRemoteDisplay.h>
+#include <media/IRemoteDisplayClient.h>
+#include <media/MediaPlayerInterface.h>
+#include <media/mediarecorder.h>
+#include <media/MediaMetadataRetrieverInterface.h>
+#include <media/Metadata.h>
+#include <media/AudioTrack.h>
+#include <media/MemoryLeakTrackUtil.h>
+#include <media/stagefright/MediaCodecList.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooperRoster.h>
+#include <mediautils/BatteryNotifier.h>
+
+//#include <memunreachable/memunreachable.h>
+#include <system/audio.h>
+
+#include <private/android_filesystem_config.h>
+
+#include "MediaAnalyticsService.h"
+
+
+namespace android {
+
+
+static int trackqueue = 0;
+
+//using android::status_t;
+//using android::OK;
+//using android::BAD_VALUE;
+//using android::NOT_ENOUGH_DATA;
+//using android::Parcel;
+
+
+void MediaAnalyticsService::instantiate() {
+    defaultServiceManager()->addService(
+            String16("media.analytics"), new MediaAnalyticsService());
+}
+
+// XXX: add dynamic controls for mMaxRecords
+MediaAnalyticsService::MediaAnalyticsService()
+        : mMaxRecords(100) {
+
+    ALOGD("MediaAnalyticsService created");
+    // clear our queues
+    mOpen = new List<sp<MediaAnalyticsItem>>();
+    mFinalized = new List<sp<MediaAnalyticsItem>>();
+
+    mItemsSubmitted = 0;
+    mItemsFinalized = 0;
+    mItemsDiscarded = 0;
+
+    mLastSessionID = 0;
+    // recover any persistency we set up
+    // etc
+}
+
+MediaAnalyticsService::~MediaAnalyticsService() {
+        ALOGD("MediaAnalyticsService destroyed");
+
+    // XXX: clean out mOpen and mFinalized
+}
+
+
+MediaAnalyticsItem::SessionID_t MediaAnalyticsService::generateUniqueSessionID() {
+    // generate a new sessionid
+
+    Mutex::Autolock _l(mLock_ids);
+    return (++mLastSessionID);
+}
+
+MediaAnalyticsItem::SessionID_t MediaAnalyticsService::submit(sp<MediaAnalyticsItem> item, bool forcenew) {
+
+    MediaAnalyticsItem::SessionID_t id = MediaAnalyticsItem::SessionIDInvalid;
+
+    // we control these, not using whatever the user might have sent
+    nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+    item->setTimestamp(now);
+    int pid = IPCThreadState::self()->getCallingPid();
+    item->setPid(pid);
+    int uid = IPCThreadState::self()->getCallingUid();
+    item->setUid(uid);
+
+    mItemsSubmitted++;
+
+    // validate the record; we discard if we don't like it
+    if (contentValid(item) == false) {
+        return MediaAnalyticsItem::SessionIDInvalid;
+    }
+
+
+    // if we have a sesisonid in the new record, look to make
+    // sure it doesn't appear in the finalized list.
+    // XXX: this is for security / DOS prevention.
+    // may also require that we persist the unique sessionIDs
+    // across boots [instead of within a single boot]
+
+
+    // match this new record up against records in the open
+    // list...
+    // if there's a match, merge them together
+    // deal with moving the old / merged record into the finalized que
+
+    bool finalizing = item->getFinalized();
+
+    // if finalizing, we'll remove it
+    sp<MediaAnalyticsItem> oitem = findItem(mOpen, item, finalizing | forcenew);
+    if (oitem != NULL) {
+        if (forcenew) {
+            // old one gets finalized, then we insert the new one
+            // so we'll have 2 records at the end of this.
+            // but don't finalize an empty record
+            if (oitem->count() != 0) {
+                oitem->setFinalized(true);
+                saveItem(mFinalized, oitem, 0);
+            }
+            // new record could itself be marked finalized...
+            if (finalizing) {
+                saveItem(mFinalized, item, 0);
+                mItemsFinalized++;
+            } else {
+                saveItem(mOpen, item, 1);
+            }
+            id = item->getSessionID();
+        } else {
+            // combine the records, send it to finalized if appropriate
+            oitem->merge(item);
+            if (finalizing) {
+                saveItem(mFinalized, oitem, 0);
+                mItemsFinalized++;
+            }
+            id = oitem->getSessionID();
+        }
+    } else {
+            // nothing to merge, save the new record
+            if (finalizing) {
+                if (item->count() != 0) {
+                    // drop empty records
+                    saveItem(mFinalized, item, 0);
+                    mItemsFinalized++;
+                }
+            } else {
+                saveItem(mOpen, item, 1);
+            }
+            id = item->getSessionID();
+    }
+
+    return id;
+}
+
+List<sp<MediaAnalyticsItem>> *MediaAnalyticsService::getMediaAnalyticsItemList(bool finished, nsecs_t ts) {
+    // this might never get called; the binder interface maps to the full parm list
+    // on the client side before making the binder call.
+    // but this lets us be sure...
+    List<sp<MediaAnalyticsItem>> *list;
+    list = getMediaAnalyticsItemList(finished, ts, MediaAnalyticsItem::kKeyAny);
+    return list;
+}
+
+List<sp<MediaAnalyticsItem>> *MediaAnalyticsService::getMediaAnalyticsItemList(bool , nsecs_t , MediaAnalyticsItem::Key ) {
+
+    // XXX: implement the get-item-list semantics
+
+    List<sp<MediaAnalyticsItem>> *list = NULL;
+    // set up our query on the persistent data
+    // slurp in all of the pieces
+    // return that
+    return list;
+}
+
+// ignoring 2nd argument, name removed to keep compiler happy
+// XXX: arguments to parse:
+//     -- a timestamp (either since X or last X seconds) to bound search
+status_t MediaAnalyticsService::dump(int fd, const Vector<String16>&)
+{
+    const size_t SIZE = 256;
+    char buffer[SIZE];
+    String8 result;
+
+    if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+        snprintf(buffer, SIZE, "Permission Denial: "
+                "can't dump MediaAnalyticsService from pid=%d, uid=%d\n",
+                IPCThreadState::self()->getCallingPid(),
+                IPCThreadState::self()->getCallingUid());
+        result.append(buffer);
+    } else {
+
+        // crack parameters
+
+
+        Mutex::Autolock _l(mLock);
+
+        snprintf(buffer, SIZE, "Dump of the mediaanalytics process:\n");
+        result.append(buffer);
+
+        int enabled = MediaAnalyticsItem::isEnabled();
+        if (enabled) {
+            snprintf(buffer, SIZE, "Analytics gathering: enabled\n");
+        } else {
+            snprintf(buffer, SIZE, "Analytics gathering: DISABLED via property\n");
+        }
+        result.append(buffer);
+
+        snprintf(buffer, SIZE,
+            "Since Boot: Submissions: %" PRId64
+	    " Finalizations: %" PRId64
+            " Discarded: %" PRId64 "\n",
+            mItemsSubmitted, mItemsFinalized, mItemsDiscarded);
+        result.append(buffer);
+
+        // show the recently recorded records
+        snprintf(buffer, sizeof(buffer), "\nFinalized Analytics (oldest first):\n");
+        result.append(buffer);
+        result.append(this->dumpQueue(mFinalized));
+
+        snprintf(buffer, sizeof(buffer), "\nIn-Progress Analytics (newest first):\n");
+        result.append(buffer);
+        result.append(this->dumpQueue(mOpen));
+
+        // show who is connected and injecting records?
+        // talk about # records fed to the 'readers'
+        // talk about # records we discarded, perhaps "discarded w/o reading" too
+
+    }
+    write(fd, result.string(), result.size());
+    return NO_ERROR;
+}
+
+// caller has locked mLock...
+String8 MediaAnalyticsService::dumpQueue(List<sp<MediaAnalyticsItem>> *theList) {
+    const size_t SIZE = 256;
+    char buffer[SIZE];
+    String8 result;
+    int slot = 0;
+
+    if (theList->empty()) {
+            result.append("empty\n");
+    } else {
+        List<sp<MediaAnalyticsItem>>::iterator it = theList->begin();
+        for (; it != theList->end(); it++, slot++) {
+            AString entry = (*it)->toString();
+            snprintf(buffer, sizeof(buffer), "%4d: %s\n",
+                        slot, entry.c_str());
+            result.append(buffer);
+        }
+    }
+
+    return result;
+}
+
+//
+// Our Cheap in-core, non-persistent records management.
+// XXX: rewrite this to manage persistence, etc.
+
+// insert appropriately into queue
+void MediaAnalyticsService::saveItem(List<sp<MediaAnalyticsItem>> *l, sp<MediaAnalyticsItem> item, int front) {
+
+    Mutex::Autolock _l(mLock);
+
+    if (false)
+        ALOGD("Inject a record: session %" PRId64 " ts %" PRId64 "",
+            item->getSessionID(), item->getTimestamp());
+
+    if (trackqueue) {
+        String8 before = dumpQueue(l);
+        ALOGD("Q before insert: %s", before.string());
+    }
+
+    // adding at back of queue (fifo order)
+    if (front)  {
+        l->push_front(item);
+    } else {
+        l->push_back(item);
+    }
+
+    if (trackqueue) {
+        String8 after = dumpQueue(l);
+        ALOGD("Q after insert: %s", after.string());
+    }
+
+    // keep removing old records the front until we're in-bounds
+    if (mMaxRecords > 0) {
+        while (l->size() > (size_t) mMaxRecords) {
+            sp<MediaAnalyticsItem> oitem = *(l->begin());
+            if (trackqueue) {
+                ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
+                    oitem->getKey().c_str(), oitem->getSessionID(),
+                    oitem->getTimestamp());
+            }
+            l->erase(l->begin());
+	    mItemsDiscarded++;
+        }
+    }
+
+    if (trackqueue) {
+        String8 after = dumpQueue(l);
+        ALOGD("Q after cleanup: %s", after.string());
+    }
+}
+
+// are they alike enough that nitem can be folded into oitem?
+static bool compatibleItems(sp<MediaAnalyticsItem> oitem, sp<MediaAnalyticsItem> nitem) {
+
+    if (0) {
+        ALOGD("Compare: o %s n %s",
+              oitem->toString().c_str(), nitem->toString().c_str());
+    }
+
+    // general safety
+    if (nitem->getUid() != oitem->getUid()) {
+        return false;
+    }
+    if (nitem->getPid() != oitem->getPid()) {
+        return false;
+    }
+
+    // key -- needs to match
+    if (nitem->getKey() == oitem->getKey()) {
+        // still in the game.
+    } else {
+        return false;
+    }
+
+    // session id -- empty field in new is allowed
+    MediaAnalyticsItem::SessionID_t osession = oitem->getSessionID();
+    MediaAnalyticsItem::SessionID_t nsession = nitem->getSessionID();
+    if (nsession != osession) {
+        // incoming '0' matches value in osession
+        if (nsession != 0) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+// find the incomplete record that this will overlay
+sp<MediaAnalyticsItem> MediaAnalyticsService::findItem(List<sp<MediaAnalyticsItem>> *theList, sp<MediaAnalyticsItem> nitem, bool removeit) {
+    sp<MediaAnalyticsItem> item;
+
+    if (nitem == NULL) {
+        return NULL;
+    }
+
+    Mutex::Autolock _l(mLock);
+
+    for (List<sp<MediaAnalyticsItem>>::iterator it = theList->begin();
+        it != theList->end(); it++) {
+        sp<MediaAnalyticsItem> tmp = (*it);
+
+        if (!compatibleItems(tmp, nitem)) {
+            continue;
+        }
+
+        // we match! this is the one I want.
+        if (removeit) {
+            theList->erase(it);
+        }
+        item = tmp;
+        break;
+    }
+    return item;
+}
+
+
+// delete the indicated record
+void MediaAnalyticsService::deleteItem(List<sp<MediaAnalyticsItem>> *l, sp<MediaAnalyticsItem> item) {
+
+    Mutex::Autolock _l(mLock);
+
+    if(trackqueue) {
+        String8 before = dumpQueue(l);
+        ALOGD("Q before delete: %s", before.string());
+    }
+
+    for (List<sp<MediaAnalyticsItem>>::iterator it = l->begin();
+        it != l->end(); it++) {
+        if ((*it)->getSessionID() != item->getSessionID())
+            continue;
+
+        ALOGD(" --- removing record for SessionID %" PRId64 "", item->getSessionID());
+        l->erase(it);
+        break;
+    }
+
+    if (trackqueue) {
+        String8 after = dumpQueue(l);
+        ALOGD("Q after delete: %s", after.string());
+    }
+}
+
+// are the contents good
+bool MediaAnalyticsService::contentValid(sp<MediaAnalyticsItem>) {
+
+    // certain keys require certain uids
+    // internal consistency
+
+    return true;
+}
+
+// are we rate limited, normally false
+bool MediaAnalyticsService::rateLimited(sp<MediaAnalyticsItem>) {
+
+    return false;
+}
+
+
+} // namespace android
diff --git a/media/libmediaanalyticsservice/MediaAnalyticsService.h b/media/libmediaanalyticsservice/MediaAnalyticsService.h
new file mode 100644
index 0000000..f9afeb2
--- /dev/null
+++ b/media/libmediaanalyticsservice/MediaAnalyticsService.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+
+#ifndef ANDROID_MEDIAANALYTICSSERVICE_H
+#define ANDROID_MEDIAANALYTICSSERVICE_H
+
+#include <arpa/inet.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+
+namespace android {
+
+class MediaAnalyticsService : public BnMediaAnalyticsService
+{
+
+ public:
+
+    virtual int64_t submit(sp<MediaAnalyticsItem> item, bool forcenew);
+
+    virtual List<sp<MediaAnalyticsItem>>
+            *getMediaAnalyticsItemList(bool finished, int64_t ts);
+    virtual List<sp<MediaAnalyticsItem>>
+            *getMediaAnalyticsItemList(bool finished, int64_t ts, MediaAnalyticsItem::Key key);
+
+
+    static  void            instantiate();
+    virtual status_t        dump(int fd, const Vector<String16>& args);
+
+                            MediaAnalyticsService();
+    virtual                 ~MediaAnalyticsService();
+
+ private:
+    MediaAnalyticsItem::SessionID_t generateUniqueSessionID();
+
+    // statistics about our analytics
+    int64_t mItemsSubmitted;
+    int64_t mItemsFinalized;
+    int64_t mItemsDiscarded;
+    MediaAnalyticsItem::SessionID_t mLastSessionID;
+
+    // partitioned a bit so we don't over serialize
+    mutable Mutex           mLock;
+    mutable Mutex           mLock_ids;
+
+    // the most we hold in memory
+    // up to this many in each queue (open, finalized)
+    int32_t mMaxRecords;
+
+    // input validation after arrival from client
+    bool contentValid(sp<MediaAnalyticsItem>);
+    bool rateLimited(sp<MediaAnalyticsItem>);
+
+    // the ones that are still open
+    // (newest at front) since we keep looking for them
+    List<sp<MediaAnalyticsItem>> *mOpen;
+    // the ones we've finalized
+    // (oldest at front) so it prints nicely for dumpsys
+    List<sp<MediaAnalyticsItem>> *mFinalized;
+    // searching within these queues: queue, key
+    sp<MediaAnalyticsItem> findItem(List<sp<MediaAnalyticsItem>> *,
+                                     sp<MediaAnalyticsItem>, bool removeit);
+
+    void saveItem(sp<MediaAnalyticsItem>);
+    void saveItem(List<sp<MediaAnalyticsItem>>*, sp<MediaAnalyticsItem>, int);
+    void deleteItem(List<sp<MediaAnalyticsItem>>*, sp<MediaAnalyticsItem>);
+
+    String8 dumpQueue(List<sp<MediaAnalyticsItem>> *);
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_MEDIAANALYTICSSERVICE_H
diff --git a/services/mediaanalytics/Android.mk b/services/mediaanalytics/Android.mk
new file mode 100644
index 0000000..76a5c1c
--- /dev/null
+++ b/services/mediaanalytics/Android.mk
@@ -0,0 +1,32 @@
+# Media Statistics service
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	main_mediaanalytics.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libcutils \
+	liblog \
+	libmedia \
+	libmediaanalyticsservice \
+	libutils \
+	libbinder \
+	libicuuc
+
+LOCAL_STATIC_LIBRARIES := \
+        libicuandroid_utils \
+        libregistermsext
+
+LOCAL_C_INCLUDES := \
+    frameworks/av/media/libmediaanalyticsservice
+
+LOCAL_MODULE:= mediaanalytics
+
+LOCAL_INIT_RC := mediaanalytics.rc
+
+LOCAL_CFLAGS := -Werror -Wall
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/mediaanalytics/main_mediaanalytics.cpp b/services/mediaanalytics/main_mediaanalytics.cpp
new file mode 100644
index 0000000..ba601ee
--- /dev/null
+++ b/services/mediaanalytics/main_mediaanalytics.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#define LOG_TAG "mediaanalytics"
+//#define LOG_NDEBUG 0
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+#include <utils/Log.h>
+//#include "RegisterExtensions.h"
+
+// from LOCAL_C_INCLUDES
+#include "IcuUtils.h"
+#include "MediaAnalyticsService.h"
+
+using namespace android;
+
+int main(int argc __unused, char **argv __unused)
+{
+    signal(SIGPIPE, SIG_IGN);
+
+    sp<ProcessState> proc(ProcessState::self());
+    sp<IServiceManager> sm(defaultServiceManager());
+    ALOGI("ServiceManager: %p", sm.get());
+
+    InitializeIcuOrDie();
+    MediaAnalyticsService::instantiate();
+    ProcessState::self()->startThreadPool();
+    IPCThreadState::self()->joinThreadPool();
+}
diff --git a/services/mediaanalytics/mediaanalytics.rc b/services/mediaanalytics/mediaanalytics.rc
new file mode 100644
index 0000000..0af69f5
--- /dev/null
+++ b/services/mediaanalytics/mediaanalytics.rc
@@ -0,0 +1,5 @@
+service mediaanalytics /system/bin/mediaanalytics
+    class main
+    user media
+    ioprio rt 4
+    writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks