blob: 5f05d5a684f2528e5f03be2edc85180b7cd513c7 [file] [log] [blame]
/*
* 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