Merge "Support multiple clients attaching to a module"
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/include/media/stagefright/DataSource.h b/include/media/stagefright/DataSource.h
index 3479f76..a8dcbe0 100644
--- a/include/media/stagefright/DataSource.h
+++ b/include/media/stagefright/DataSource.h
@@ -44,6 +44,7 @@
         kStreamedFromLocalHost = 2,
         kIsCachingDataSource   = 4,
         kIsHTTPBasedSource     = 8,
+        kIsLocalFileSource     = 16,
     };
 
     static sp<DataSource> CreateFromURI(
diff --git a/include/media/stagefright/FileSource.h b/include/media/stagefright/FileSource.h
index b6349e0..9f3bb5e 100644
--- a/include/media/stagefright/FileSource.h
+++ b/include/media/stagefright/FileSource.h
@@ -39,6 +39,10 @@
 
     virtual status_t getSize(off64_t *size);
 
+    virtual uint32_t flags() {
+        return kIsLocalFileSource;
+    }
+
     virtual sp<DecryptHandle> DrmInitialization(const char *mime);
 
     virtual void getDrmInfo(sp<DecryptHandle> &handle, DrmManagerClient **client);
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index 3c7e8b7..aef7dfb 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -671,6 +671,8 @@
         mState = STATE_STOPPING;
     } else {
         mState = STATE_STOPPED;
+        ALOGD_IF(mSharedBuffer == nullptr,
+                "stop() called with %u frames delivered", mReleased.value());
         mReleased = 0;
     }
 
diff --git a/media/libaudiohal/Android.mk b/media/libaudiohal/Android.mk
index 2d6b3f5..63aa9c6 100644
--- a/media/libaudiohal/Android.mk
+++ b/media/libaudiohal/Android.mk
@@ -22,9 +22,10 @@
     StreamHalHidl.cpp
 
 LOCAL_SHARED_LIBRARIES += \
-    libhwbinder \
-    libhidlbase \
-    libbase     \
+    libhwbinder      \
+    libhidlbase      \
+    libhidltransport \
+    libbase          \
     android.hardware.audio@2.0             \
     android.hardware.audio.common@2.0      \
     android.hardware.audio.common@2.0-util \
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/media/libstagefright/include/MPEG2TSExtractor.h b/media/libstagefright/include/MPEG2TSExtractor.h
index 93e9a4b..ef55620 100644
--- a/media/libstagefright/include/MPEG2TSExtractor.h
+++ b/media/libstagefright/include/MPEG2TSExtractor.h
@@ -89,6 +89,8 @@
     // Add a SynPoint derived from |event|.
     void addSyncPoint_l(const ATSParser::SyncEvent &event);
 
+    status_t  estimateDurationsFromTimesUsAtEnd();
+
     DISALLOW_EVIL_CONSTRUCTORS(MPEG2TSExtractor);
 };
 
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index 844479e..4975d9a 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -90,6 +90,10 @@
         return mParser->mFlags;
     }
 
+    uint64_t firstPTS() const {
+        return mFirstPTS;
+    }
+
 private:
     struct StreamInfo {
         unsigned mType;
@@ -135,6 +139,7 @@
 
     void signalEOS(status_t finalResult);
 
+    SourceType getSourceType();
     sp<MediaSource> getSource(SourceType type);
 
     bool isAudio() const;
@@ -208,11 +213,12 @@
     : mHasReturnedData(false), mOffset(offset), mTimeUs(0) {}
 
 void ATSParser::SyncEvent::init(off64_t offset, const sp<MediaSource> &source,
-        int64_t timeUs) {
+        int64_t timeUs, SourceType type) {
     mHasReturnedData = true;
     mOffset = offset;
     mMediaSource = source;
     mTimeUs = timeUs;
+    mType = type;
 }
 
 void ATSParser::SyncEvent::reset() {
@@ -1121,13 +1127,24 @@
                 int64_t timeUs;
                 if (accessUnit->meta()->findInt64("timeUs", &timeUs)) {
                     found = true;
-                    event->init(pesStartOffset, mSource, timeUs);
+                    event->init(pesStartOffset, mSource, timeUs, getSourceType());
                 }
             }
         }
     }
 }
 
+ATSParser::SourceType ATSParser::Stream::getSourceType() {
+    if (isVideo()) {
+        return VIDEO;
+    } else if (isAudio()) {
+        return AUDIO;
+    } else if (isMeta()) {
+        return META;
+    }
+    return NUM_SOURCE_TYPES;
+}
+
 sp<MediaSource> ATSParser::Stream::getSource(SourceType type) {
     switch (type) {
         case VIDEO:
@@ -1565,6 +1582,16 @@
     return mPrograms.editItemAt(0)->PTSTimeDeltaEstablished();
 }
 
+int64_t ATSParser::getFirstPTSTimeUs() {
+    for (size_t i = 0; i < mPrograms.size(); ++i) {
+        sp<ATSParser::Program> program = mPrograms.itemAt(i);
+        if (program->PTSTimeDeltaEstablished()) {
+            return (program->firstPTS() * 100) / 9;
+        }
+    }
+    return -1;
+}
+
 __attribute__((no_sanitize("integer")))
 void ATSParser::updatePCR(
         unsigned /* PID */, uint64_t PCR, uint64_t byteOffsetFromStart) {
diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h
index 2b166f0..faae6c9 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.h
+++ b/media/libstagefright/mpeg2ts/ATSParser.h
@@ -62,18 +62,26 @@
         ALIGNED_VIDEO_DATA         = 2,
     };
 
+    enum SourceType {
+        VIDEO = 0,
+        AUDIO = 1,
+        META  = 2,
+        NUM_SOURCE_TYPES = 3
+    };
+
     // Event is used to signal sync point event at feedTSPacket().
     struct SyncEvent {
         explicit SyncEvent(off64_t offset);
 
         void init(off64_t offset, const sp<MediaSource> &source,
-                int64_t timeUs);
+                int64_t timeUs, SourceType type);
 
         bool hasReturnedData() const { return mHasReturnedData; }
         void reset();
         off64_t getOffset() const { return mOffset; }
         const sp<MediaSource> &getMediaSource() const { return mMediaSource; }
         int64_t getTimeUs() const { return mTimeUs; }
+        SourceType getType() const { return mType; }
 
     private:
         bool mHasReturnedData;
@@ -87,6 +95,7 @@
         sp<MediaSource> mMediaSource;
         /* The timestamp of the sync frame. */
         int64_t mTimeUs;
+        SourceType mType;
     };
 
     explicit ATSParser(uint32_t flags = 0);
@@ -107,17 +116,13 @@
 
     void signalEOS(status_t finalResult);
 
-    enum SourceType {
-        VIDEO = 0,
-        AUDIO = 1,
-        META  = 2,
-        NUM_SOURCE_TYPES = 3
-    };
     sp<MediaSource> getSource(SourceType type);
     bool hasSource(SourceType type) const;
 
     bool PTSTimeDeltaEstablished();
 
+    int64_t getFirstPTSTimeUs();
+
     enum {
         // From ISO/IEC 13818-1: 2000 (E), Table 2-29
         STREAMTYPE_RESERVED             = 0x00,
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index 4fcf7b5..548f44e 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -44,6 +44,7 @@
       mEnabled(true),
       mFormat(NULL),
       mLastQueuedTimeUs(0),
+      mEstimatedBufferDurationUs(-1),
       mEOSResult(OK),
       mLatestEnqueuedMeta(NULL),
       mLatestDequeuedMeta(NULL) {
@@ -309,6 +310,8 @@
 
     mFormat = NULL;
     mLatestEnqueuedMeta = NULL;
+
+    mEstimatedBufferDurationUs = -1;
 }
 
 void AnotherPacketSource::queueDiscontinuity(
@@ -431,6 +434,31 @@
     return durationUs;
 }
 
+int64_t AnotherPacketSource::getEstimatedBufferDurationUs() {
+    Mutex::Autolock autoLock(mLock);
+    if (mEstimatedBufferDurationUs >= 0) {
+        return mEstimatedBufferDurationUs;
+    }
+
+    SortedVector<int64_t> maxTimesUs;
+    List<sp<ABuffer> >::iterator it;
+    int64_t t1 = 0, t2 = 0;
+    for (it = mBuffers.begin(); it != mBuffers.end(); ++it) {
+        int64_t timeUs = 0;
+        const sp<ABuffer> &buffer = *it;
+        if (!buffer->meta()->findInt64("timeUs", &timeUs)) {
+            continue;
+        }
+        maxTimesUs.add(timeUs);
+        while (maxTimesUs.size() > 2) {
+            maxTimesUs.removeAt(0);
+            t1 = maxTimesUs.itemAt(0);
+            t2 = maxTimesUs.itemAt(1);
+        }
+    }
+    return mEstimatedBufferDurationUs = t2 - t1;
+}
+
 status_t AnotherPacketSource::nextBufferTime(int64_t *timeUs) {
     *timeUs = 0;
 
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
index dd6849e..b0890d7 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
@@ -57,6 +57,9 @@
     // presentation timestamps since the last discontinuity (if any).
     int64_t getBufferedDurationUs(status_t *finalResult);
 
+    // Returns the difference between the two largest timestamps queued
+    int64_t getEstimatedBufferDurationUs();
+
     status_t nextBufferTime(int64_t *timeUs);
 
     void queueAccessUnit(const sp<ABuffer> &buffer);
@@ -113,6 +116,7 @@
     bool mEnabled;
     sp<MetaData> mFormat;
     int64_t mLastQueuedTimeUs;
+    int64_t mEstimatedBufferDurationUs;
     List<sp<ABuffer> > mBuffers;
     status_t mEOSResult;
     sp<AMessage> mLatestEnqueuedMeta;
diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
index 116a5bc..bde33dc 100644
--- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
+++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
@@ -26,6 +26,7 @@
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AUtils.h>
 #include <media/stagefright/DataSource.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaErrors.h>
@@ -40,6 +41,8 @@
 namespace android {
 
 static const size_t kTSPacketSize = 188;
+static const int kMaxDurationReadSize = 250000LL;
+static const int kMaxDurationRetry = 6;
 
 struct MPEG2TSSource : public MediaSource {
     MPEG2TSSource(
@@ -243,6 +246,8 @@
             const sp<MetaData> meta = impl->getFormat();
             meta->setInt64(kKeyDuration, durationUs);
             impl->setFormat(meta);
+        } else {
+            estimateDurationsFromTimesUsAtEnd();
         }
     }
 
@@ -301,6 +306,106 @@
     }
 }
 
+status_t MPEG2TSExtractor::estimateDurationsFromTimesUsAtEnd()  {
+    if (!(mDataSource->flags() & DataSource::kIsLocalFileSource)) {
+        return ERROR_UNSUPPORTED;
+    }
+
+    off64_t size = 0;
+    status_t err = mDataSource->getSize(&size);
+    if (err != OK) {
+        return err;
+    }
+
+    uint8_t packet[kTSPacketSize];
+    const off64_t zero = 0;
+    off64_t offset = max(zero, size - kMaxDurationReadSize);
+    if (mDataSource->readAt(offset, &packet, 0) < 0) {
+        return ERROR_IO;
+    }
+
+    int retry = 0;
+    bool allDurationsFound = false;
+    int64_t timeAnchorUs = mParser->getFirstPTSTimeUs();
+    do {
+        int bytesRead = 0;
+        sp<ATSParser> parser = new ATSParser(ATSParser::TS_TIMESTAMPS_ARE_ABSOLUTE);
+        ATSParser::SyncEvent ev(0);
+        offset = max(zero, size - (kMaxDurationReadSize << retry));
+        offset = (offset / kTSPacketSize) * kTSPacketSize;
+        for (;;) {
+            if (bytesRead >= kMaxDurationReadSize << max(0, retry - 1)) {
+                break;
+            }
+
+            ssize_t n = mDataSource->readAt(offset, packet, kTSPacketSize);
+            if (n < 0) {
+                return n;
+            } else if (n < (ssize_t)kTSPacketSize) {
+                break;
+            }
+
+            offset += kTSPacketSize;
+            bytesRead += kTSPacketSize;
+            err = parser->feedTSPacket(packet, kTSPacketSize, &ev);
+            if (err != OK) {
+                return err;
+            }
+
+            if (ev.hasReturnedData()) {
+                int64_t durationUs = ev.getTimeUs();
+                ATSParser::SourceType type = ev.getType();
+                ev.reset();
+
+                int64_t firstTimeUs;
+                sp<AnotherPacketSource> src =
+                    (AnotherPacketSource *)mParser->getSource(type).get();
+                if (src == NULL || src->nextBufferTime(&firstTimeUs) != OK) {
+                    continue;
+                }
+                durationUs += src->getEstimatedBufferDurationUs();
+                durationUs -= timeAnchorUs;
+                durationUs -= firstTimeUs;
+                if (durationUs > 0) {
+                    int64_t origDurationUs, lastDurationUs;
+                    const sp<MetaData> meta = src->getFormat();
+                    const uint32_t kKeyLastDuration = 'ldur';
+                    // Require two consecutive duration calculations to be within 1 sec before
+                    // updating; use MetaData to store previous duration estimate in per-stream
+                    // context.
+                    if (!meta->findInt64(kKeyDuration, &origDurationUs)
+                            || !meta->findInt64(kKeyLastDuration, &lastDurationUs)
+                            || (origDurationUs < durationUs
+                             && abs(durationUs - lastDurationUs) < 60000000)) {
+                        meta->setInt64(kKeyDuration, durationUs);
+                    }
+                    meta->setInt64(kKeyLastDuration, durationUs);
+                }
+            }
+        }
+
+        if (!allDurationsFound) {
+            allDurationsFound = true;
+            for (auto t: {ATSParser::VIDEO, ATSParser::AUDIO}) {
+                sp<AnotherPacketSource> src = (AnotherPacketSource *)mParser->getSource(t).get();
+                if (src == NULL) {
+                    continue;
+                }
+                int64_t durationUs;
+                const sp<MetaData> meta = src->getFormat();
+                if (!meta->findInt64(kKeyDuration, &durationUs)) {
+                    allDurationsFound = false;
+                    break;
+                }
+            }
+        }
+
+        ++retry;
+    } while(!allDurationsFound && offset > 0 && retry <= kMaxDurationRetry);
+
+    return allDurationsFound? OK : ERROR_UNSUPPORTED;
+}
+
 uint32_t MPEG2TSExtractor::flags() const {
     return CAN_PAUSE | CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD;
 }
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index afc2d22..c3bf1f9 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -19,6 +19,7 @@
 #define ANDROID_AUDIO_FLINGER_H
 
 #include "Configuration.h"
+#include <deque>
 #include <stdint.h>
 #include <sys/types.h>
 #include <limits.h>
diff --git a/services/audioflinger/Effects.h b/services/audioflinger/Effects.h
index 47cf7b1..d745121 100644
--- a/services/audioflinger/Effects.h
+++ b/services/audioflinger/Effects.h
@@ -25,10 +25,11 @@
 // state changes or resource modifications. Always respect the following order
 // if multiple mutexes must be acquired to avoid cross deadlock:
 // AudioFlinger -> ThreadBase -> EffectChain -> EffectModule
+// AudioHandle -> ThreadBase -> EffectChain -> EffectModule
 // In addition, methods that lock the AudioPolicyService mutex (getOutputForEffect(),
-// startOutput()...) should never be called with AudioFlinger or Threadbase mutex locked
-// to avoid cross deadlock with other clients calling AudioPolicyService methods that in turn
-// call AudioFlinger thus locking the same mutexes in the reverse order.
+// startOutput(), getInputForAttr(), releaseInput()...) should never be called with AudioFlinger or
+// Threadbase mutex locked to avoid cross deadlock with other clients calling AudioPolicyService
+// methods that in turn call AudioFlinger thus locking the same mutexes in the reverse order.
 
 // The EffectModule class is a wrapper object controlling the effect engine implementation
 // in the effect library. It prevents concurrent calls to process() and command() functions
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index bc774a3..9966eeb 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1623,6 +1623,7 @@
     dumpInternals(fd, args);
     dumpTracks(fd, args);
     dumpEffectChains(fd, args);
+    mLocalLog.dump(fd, args, "  " /* prefix */);
 }
 
 void AudioFlinger::PlaybackThread::dumpTracks(int fd, const Vector<String16>& args __unused)
@@ -2093,6 +2094,10 @@
             chain->incActiveTrackCnt();
         }
 
+        char buffer[256];
+        track->dump(buffer, ARRAY_SIZE(buffer), false /* active */);
+        mLocalLog.log("addTrack_l    (%p) %s", track.get(), buffer + 4); // log for analysis
+
         status = NO_ERROR;
     }
 
@@ -2118,6 +2123,11 @@
 void AudioFlinger::PlaybackThread::removeTrack_l(const sp<Track>& track)
 {
     track->triggerEvents(AudioSystem::SYNC_EVENT_PRESENTATION_COMPLETE);
+
+    char buffer[256];
+    track->dump(buffer, ARRAY_SIZE(buffer), false /* active */);
+    mLocalLog.log("removeTrack_l (%p) %s", track.get(), buffer + 4); // log for analysis
+
     mTracks.remove(track);
     deleteTrackName_l(track->name());
     // redundant as track is about to be destroyed, for dumpsys only
@@ -3285,6 +3295,10 @@
             }
             if (track->isTerminated()) {
                 removeTrack_l(track);
+            } else { // inactive but not terminated
+                char buffer[256];
+                track->dump(buffer, ARRAY_SIZE(buffer), false /* active */);
+                mLocalLog.log("removeTracks_l(%p) %s", track.get(), buffer + 4);
             }
         }
     }
@@ -3731,6 +3745,15 @@
         FastMixerStateQueue *sq = mFastMixer->sq();
         FastMixerState *state = sq->begin();
         if (!(state->mCommand & FastMixerState::IDLE)) {
+            // Report any frames trapped in the Monopipe
+            MonoPipe *monoPipe = (MonoPipe *)mPipeSink.get();
+            const long long pipeFrames = monoPipe->maxFrames() - monoPipe->availableToWrite();
+            mLocalLog.log("threadLoop_standby: framesWritten:%lld  suspendedFrames:%lld  "
+                    "monoPipeWritten:%lld  monoPipeLeft:%lld",
+                    (long long)mFramesWritten, (long long)mSuspendedFrames,
+                    (long long)mPipeSink->framesWritten(), pipeFrames);
+            mLocalLog.log("threadLoop_standby: %s", mTimestamp.toString().c_str());
+
             state->mCommand = FastMixerState::COLD_IDLE;
             state->mColdFutexAddr = &mFastMixerFutex;
             state->mColdGen++;
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index eb4ac26..d261ea5 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -864,6 +864,78 @@
     uint32_t                mScreenState;   // cached copy of gScreenState
     static const size_t     kFastMixerLogSize = 4 * 1024;
     sp<NBLog::Writer>       mFastMixerNBLogWriter;
+
+    // Do not call from a sched_fifo thread as it uses a system time call
+    // and obtains a local mutex.
+    class LocalLog {
+    public:
+        void log(const char *fmt, ...) {
+            va_list val;
+            va_start(val, fmt);
+
+            // format to buffer
+            char buffer[512];
+            int length = vsnprintf(buffer, sizeof(buffer), fmt, val);
+            if (length >= (signed)sizeof(buffer)) {
+                length = sizeof(buffer) - 1;
+            }
+
+            // strip out trailing newline
+            while (length > 0 && buffer[length - 1] == '\n') {
+                buffer[--length] = 0;
+            }
+
+            // store in circular array
+            AutoMutex _l(mLock);
+            mLog.emplace_back(
+                    std::make_pair(systemTime(SYSTEM_TIME_REALTIME), std::string(buffer)));
+            if (mLog.size() > kLogSize) {
+                mLog.pop_front();
+            }
+
+            va_end(val);
+        }
+
+        void dump(int fd, const Vector<String16>& args, const char *prefix = "") {
+            if (!AudioFlinger::dumpTryLock(mLock)) return; // a local lock, shouldn't happen
+            if (mLog.size() > 0) {
+                bool dumpAll = false;
+                for (const auto &arg : args) {
+                    if (arg == String16("--locallog")) {
+                        dumpAll = true;
+                    }
+                }
+
+                dprintf(fd, "Local Log:\n");
+                auto it = mLog.begin();
+                if (!dumpAll && mLog.size() > kLogPrint) {
+                    it += (mLog.size() - kLogPrint);
+                }
+                for (; it != mLog.end(); ++it) {
+                    const int64_t ns = it->first;
+                    const int ns_per_sec = 1000000000;
+                    const time_t sec = ns / ns_per_sec;
+                    struct tm tm;
+                    localtime_r(&sec, &tm);
+
+                    dprintf(fd, "%s%02d-%02d %02d:%02d:%02d.%03d %s\n",
+                            prefix,
+                            tm.tm_mon + 1, // localtime_r uses months in 0 - 11 range
+                            tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+                            (int)(ns % ns_per_sec / 1000000),
+                            it->second.c_str());
+                }
+            }
+            mLock.unlock();
+        }
+
+    private:
+        Mutex mLock;
+        static const size_t kLogSize = 256; // full history
+        static const size_t kLogPrint = 32; // default print history
+        std::deque<std::pair<int64_t, std::string>> mLog;
+    } mLocalLog;
+
 public:
     virtual     bool        hasFastMixer() const = 0;
     virtual     FastTrackUnderruns getFastTrackUnderruns(size_t fastIndex __unused) const
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 8f134c1..e8e27e4 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -459,7 +459,7 @@
 /*static*/ void AudioFlinger::PlaybackThread::Track::appendDumpHeader(String8& result)
 {
     result.append("    Name Active Client Type      Fmt Chn mask Session fCount S F SRate  "
-                  "L dB  R dB    Server Main buf  Aux Buf Flags UndFrmCnt\n");
+                  "L dB  R dB    Server Main buf  Aux buf Flags UndFrmCnt  Flushed\n");
 }
 
 void AudioFlinger::PlaybackThread::Track::dump(char* buffer, size_t size, bool active)
@@ -526,7 +526,7 @@
         break;
     }
     snprintf(&buffer[8], size-8, " %6s %6u %4u %08X %08X %7u %6zu %1c %1d %5u %5.2g %5.2g  "
-                                 "%08X %p %p 0x%03X %9u%c\n",
+                                 "%08X %08zX %08zX 0x%03X %9u%c %7u\n",
             active ? "yes" : "no",
             (mClient == 0) ? getpid_cached : mClient->pid(),
             mStreamType,
@@ -540,11 +540,12 @@
             20.0 * log10(float_from_gain(gain_minifloat_unpack_left(vlr))),
             20.0 * log10(float_from_gain(gain_minifloat_unpack_right(vlr))),
             mCblk->mServer,
-            mMainBuffer,
-            mAuxBuffer,
+            (size_t)mMainBuffer, // use %zX as %p appends 0x
+            (size_t)mAuxBuffer,  // use %zX as %p appends 0x
             mCblk->mFlags,
             mAudioTrackServerProxy->getUnderrunFrames(),
-            nowInUnderrun);
+            nowInUnderrun,
+            (unsigned)mAudioTrackServerProxy->framesFlushed() % 10000000); // 7 digits
 }
 
 uint32_t AudioFlinger::PlaybackThread::Track::sampleRate() const {
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