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