You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
887 lines
25 KiB
887 lines
25 KiB
/*
|
|
* 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 <inttypes.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <binder/Parcel.h>
|
|
#include <utils/Errors.h>
|
|
#include <utils/Log.h>
|
|
#include <utils/Mutex.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>
|
|
#include <private/android_filesystem_config.h>
|
|
|
|
namespace android {
|
|
|
|
#define DEBUG_SERVICEACCESS 0
|
|
#define DEBUG_API 0
|
|
#define DEBUG_ALLOCATIONS 0
|
|
|
|
// after this many failed attempts, we stop trying [from this process] and just say that
|
|
// the service is off.
|
|
#define SVC_TRIES 2
|
|
|
|
// the few universal keys we have
|
|
const MediaAnalyticsItem::Key MediaAnalyticsItem::kKeyAny = "any";
|
|
const MediaAnalyticsItem::Key MediaAnalyticsItem::kKeyNone = "none";
|
|
|
|
const char * const MediaAnalyticsItem::EnabledProperty = "media.metrics.enabled";
|
|
const char * const MediaAnalyticsItem::EnabledPropertyPersist = "persist.media.metrics.enabled";
|
|
const int MediaAnalyticsItem::EnabledProperty_default = 1;
|
|
|
|
|
|
// access functions for the class
|
|
MediaAnalyticsItem::MediaAnalyticsItem()
|
|
: mPid(-1),
|
|
mUid(-1),
|
|
mSessionID(MediaAnalyticsItem::SessionIDNone),
|
|
mTimestamp(0),
|
|
mFinalized(0),
|
|
mPropCount(0), mPropSize(0), mProps(NULL)
|
|
{
|
|
mKey = MediaAnalyticsItem::kKeyNone;
|
|
}
|
|
|
|
MediaAnalyticsItem::MediaAnalyticsItem(MediaAnalyticsItem::Key key)
|
|
: mPid(-1),
|
|
mUid(-1),
|
|
mSessionID(MediaAnalyticsItem::SessionIDNone),
|
|
mTimestamp(0),
|
|
mFinalized(0),
|
|
mPropCount(0), mPropSize(0), mProps(NULL)
|
|
{
|
|
if (DEBUG_ALLOCATIONS) {
|
|
ALOGD("Allocate MediaAnalyticsItem @ %p", this);
|
|
}
|
|
mKey = key;
|
|
}
|
|
|
|
MediaAnalyticsItem::~MediaAnalyticsItem() {
|
|
if (DEBUG_ALLOCATIONS) {
|
|
ALOGD("Destroy MediaAnalyticsItem @ %p", this);
|
|
}
|
|
clear();
|
|
}
|
|
|
|
void MediaAnalyticsItem::clear() {
|
|
|
|
// clean allocated storage from key
|
|
mKey.clear();
|
|
|
|
// clean various major parameters
|
|
mSessionID = MediaAnalyticsItem::SessionIDNone;
|
|
|
|
// clean attributes
|
|
// contents of the attributes
|
|
for (size_t i = 0 ; i < mPropSize; i++ ) {
|
|
clearProp(&mProps[i]);
|
|
}
|
|
// the attribute records themselves
|
|
if (mProps != NULL) {
|
|
free(mProps);
|
|
mProps = NULL;
|
|
}
|
|
mPropSize = 0;
|
|
mPropCount = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
// make a deep copy of myself
|
|
MediaAnalyticsItem *MediaAnalyticsItem::dup() {
|
|
MediaAnalyticsItem *dst = new MediaAnalyticsItem(this->mKey);
|
|
|
|
if (dst != NULL) {
|
|
// key as part of constructor
|
|
dst->mPid = this->mPid;
|
|
dst->mUid = this->mUid;
|
|
dst->mSessionID = this->mSessionID;
|
|
dst->mTimestamp = this->mTimestamp;
|
|
dst->mFinalized = this->mFinalized;
|
|
|
|
// properties aka attributes
|
|
dst->growProps(this->mPropCount);
|
|
for(size_t i=0;i<mPropCount;i++) {
|
|
copyProp(&dst->mProps[i], &this->mProps[i]);
|
|
}
|
|
dst->mPropCount = this->mPropCount;
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
// 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() {
|
|
|
|
if (mSessionID == SessionIDNone) {
|
|
// get one from the server
|
|
MediaAnalyticsItem::SessionID_t newid = SessionIDNone;
|
|
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;
|
|
}
|
|
|
|
// this key is for the overall record -- "codec", "player", "drm", etc
|
|
MediaAnalyticsItem &MediaAnalyticsItem::setKey(MediaAnalyticsItem::Key key) {
|
|
mKey = key;
|
|
return *this;
|
|
}
|
|
|
|
MediaAnalyticsItem::Key MediaAnalyticsItem::getKey() {
|
|
return mKey;
|
|
}
|
|
|
|
// number of attributes we have in this record
|
|
int32_t MediaAnalyticsItem::count() const {
|
|
return mPropCount;
|
|
}
|
|
|
|
// find the proper entry in the list
|
|
size_t MediaAnalyticsItem::findPropIndex(const char *name, size_t len)
|
|
{
|
|
size_t i = 0;
|
|
for (; i < mPropCount; i++) {
|
|
Prop *prop = &mProps[i];
|
|
if (prop->mNameLen != len) {
|
|
continue;
|
|
}
|
|
if (memcmp(name, prop->mName, len) == 0) {
|
|
break;
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
MediaAnalyticsItem::Prop *MediaAnalyticsItem::findProp(const char *name) {
|
|
size_t len = strlen(name);
|
|
size_t i = findPropIndex(name, len);
|
|
if (i < mPropCount) {
|
|
return &mProps[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void MediaAnalyticsItem::Prop::setName(const char *name, size_t len) {
|
|
mNameLen = len;
|
|
mName = (const char *) malloc(len+1);
|
|
memcpy ((void *)mName, name, len+1);
|
|
}
|
|
|
|
// used only as part of a storing operation
|
|
MediaAnalyticsItem::Prop *MediaAnalyticsItem::allocateProp(const char *name) {
|
|
size_t len = strlen(name);
|
|
size_t i = findPropIndex(name, len);
|
|
Prop *prop;
|
|
|
|
if (i < mPropCount) {
|
|
prop = &mProps[i];
|
|
} else {
|
|
if (i == mPropSize) {
|
|
growProps();
|
|
// XXX: verify success
|
|
}
|
|
i = mPropCount++;
|
|
prop = &mProps[i];
|
|
prop->setName(name, len);
|
|
}
|
|
|
|
return prop;
|
|
}
|
|
|
|
// set the values
|
|
void MediaAnalyticsItem::setInt32(MediaAnalyticsItem::Attr name, int32_t value) {
|
|
Prop *prop = allocateProp(name);
|
|
prop->mType = kTypeInt32;
|
|
prop->u.int32Value = value;
|
|
}
|
|
|
|
void MediaAnalyticsItem::setInt64(MediaAnalyticsItem::Attr name, int64_t value) {
|
|
Prop *prop = allocateProp(name);
|
|
prop->mType = kTypeInt64;
|
|
prop->u.int64Value = value;
|
|
}
|
|
|
|
void MediaAnalyticsItem::setDouble(MediaAnalyticsItem::Attr name, double value) {
|
|
Prop *prop = allocateProp(name);
|
|
prop->mType = kTypeDouble;
|
|
prop->u.doubleValue = value;
|
|
}
|
|
|
|
void MediaAnalyticsItem::setCString(MediaAnalyticsItem::Attr name, const char *value) {
|
|
|
|
Prop *prop = allocateProp(name);
|
|
// any old value will be gone
|
|
prop->mType = kTypeCString;
|
|
prop->u.CStringValue = strdup(value);
|
|
}
|
|
|
|
void MediaAnalyticsItem::setRate(MediaAnalyticsItem::Attr name, int64_t count, int64_t duration) {
|
|
Prop *prop = allocateProp(name);
|
|
prop->mType = kTypeRate;
|
|
prop->u.rate.count = count;
|
|
prop->u.rate.duration = duration;
|
|
}
|
|
|
|
|
|
// find/add/set fused into a single operation
|
|
void MediaAnalyticsItem::addInt32(MediaAnalyticsItem::Attr name, int32_t value) {
|
|
Prop *prop = allocateProp(name);
|
|
switch (prop->mType) {
|
|
case kTypeInt32:
|
|
prop->u.int32Value += value;
|
|
break;
|
|
default:
|
|
clearPropValue(prop);
|
|
prop->mType = kTypeInt32;
|
|
prop->u.int32Value = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MediaAnalyticsItem::addInt64(MediaAnalyticsItem::Attr name, int64_t value) {
|
|
Prop *prop = allocateProp(name);
|
|
switch (prop->mType) {
|
|
case kTypeInt64:
|
|
prop->u.int64Value += value;
|
|
break;
|
|
default:
|
|
clearPropValue(prop);
|
|
prop->mType = kTypeInt64;
|
|
prop->u.int64Value = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MediaAnalyticsItem::addRate(MediaAnalyticsItem::Attr name, int64_t count, int64_t duration) {
|
|
Prop *prop = allocateProp(name);
|
|
switch (prop->mType) {
|
|
case kTypeRate:
|
|
prop->u.rate.count += count;
|
|
prop->u.rate.duration += duration;
|
|
break;
|
|
default:
|
|
clearPropValue(prop);
|
|
prop->mType = kTypeRate;
|
|
prop->u.rate.count = count;
|
|
prop->u.rate.duration = duration;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MediaAnalyticsItem::addDouble(MediaAnalyticsItem::Attr name, double value) {
|
|
Prop *prop = allocateProp(name);
|
|
switch (prop->mType) {
|
|
case kTypeDouble:
|
|
prop->u.doubleValue += value;
|
|
break;
|
|
default:
|
|
clearPropValue(prop);
|
|
prop->mType = kTypeDouble;
|
|
prop->u.doubleValue = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// find & extract values
|
|
bool MediaAnalyticsItem::getInt32(MediaAnalyticsItem::Attr name, int32_t *value) {
|
|
Prop *prop = findProp(name);
|
|
if (prop == NULL || prop->mType != kTypeInt32) {
|
|
return false;
|
|
}
|
|
if (value != NULL) {
|
|
*value = prop->u.int32Value;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MediaAnalyticsItem::getInt64(MediaAnalyticsItem::Attr name, int64_t *value) {
|
|
Prop *prop = findProp(name);
|
|
if (prop == NULL || prop->mType != kTypeInt64) {
|
|
return false;
|
|
}
|
|
if (value != NULL) {
|
|
*value = prop->u.int64Value;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MediaAnalyticsItem::getRate(MediaAnalyticsItem::Attr name, int64_t *count, int64_t *duration, double *rate) {
|
|
Prop *prop = findProp(name);
|
|
if (prop == NULL || prop->mType != kTypeRate) {
|
|
return false;
|
|
}
|
|
if (count != NULL) {
|
|
*count = prop->u.rate.count;
|
|
}
|
|
if (duration != NULL) {
|
|
*duration = prop->u.rate.duration;
|
|
}
|
|
if (rate != NULL) {
|
|
double r = 0.0;
|
|
if (prop->u.rate.duration != 0) {
|
|
r = prop->u.rate.count / (double) prop->u.rate.duration;
|
|
}
|
|
*rate = r;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MediaAnalyticsItem::getDouble(MediaAnalyticsItem::Attr name, double *value) {
|
|
Prop *prop = findProp(name);
|
|
if (prop == NULL || prop->mType != kTypeDouble) {
|
|
return false;
|
|
}
|
|
if (value != NULL) {
|
|
*value = prop->u.doubleValue;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// caller responsible for the returned string
|
|
bool MediaAnalyticsItem::getCString(MediaAnalyticsItem::Attr name, char **value) {
|
|
Prop *prop = findProp(name);
|
|
if (prop == NULL || prop->mType != kTypeDouble) {
|
|
return false;
|
|
}
|
|
if (value != NULL) {
|
|
*value = strdup(prop->u.CStringValue);
|
|
}
|
|
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 || n <= 0) {
|
|
return -1;
|
|
}
|
|
for (ssize_t i = 0 ; i < n ; i++) {
|
|
const char *name = attrs[i];
|
|
size_t len = strlen(name);
|
|
size_t j = findPropIndex(name, len);
|
|
if (j >= mPropCount) {
|
|
// not there
|
|
continue;
|
|
} else if (j+1 == mPropCount) {
|
|
// last one, shorten
|
|
zapped++;
|
|
clearProp(&mProps[j]);
|
|
mPropCount--;
|
|
} else {
|
|
// in the middle, bring last one down and shorten
|
|
zapped++;
|
|
clearProp(&mProps[j]);
|
|
mProps[j] = mProps[mPropCount-1];
|
|
mPropCount--;
|
|
}
|
|
}
|
|
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 || n <= 0) {
|
|
return -1;
|
|
}
|
|
for (ssize_t i = mPropCount-1 ; i >=0 ; i--) {
|
|
Prop *prop = &mProps[i];
|
|
for (ssize_t j = 0; j < n ; j++) {
|
|
if (strcmp(prop->mName, attrs[j]) == 0) {
|
|
clearProp(prop);
|
|
zapped++;
|
|
if (i != (ssize_t)(mPropCount-1)) {
|
|
*prop = mProps[mPropCount-1];
|
|
}
|
|
initProp(&mProps[mPropCount-1]);
|
|
mPropCount--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return zapped;
|
|
}
|
|
|
|
// remove a single key
|
|
// return value is 0 (not found) or 1 (found and removed)
|
|
int32_t MediaAnalyticsItem::filter(MediaAnalyticsItem::Attr name) {
|
|
return filter(1, &name);
|
|
}
|
|
|
|
// handle individual items/properties stored within the class
|
|
//
|
|
|
|
void MediaAnalyticsItem::initProp(Prop *prop) {
|
|
if (prop != NULL) {
|
|
prop->mName = NULL;
|
|
prop->mNameLen = 0;
|
|
|
|
prop->mType = kTypeNone;
|
|
}
|
|
}
|
|
|
|
void MediaAnalyticsItem::clearProp(Prop *prop)
|
|
{
|
|
if (prop != NULL) {
|
|
if (prop->mName != NULL) {
|
|
free((void *)prop->mName);
|
|
prop->mName = NULL;
|
|
prop->mNameLen = 0;
|
|
}
|
|
|
|
clearPropValue(prop);
|
|
}
|
|
}
|
|
|
|
void MediaAnalyticsItem::clearPropValue(Prop *prop)
|
|
{
|
|
if (prop != NULL) {
|
|
if (prop->mType == kTypeCString && prop->u.CStringValue != NULL) {
|
|
free(prop->u.CStringValue);
|
|
prop->u.CStringValue = NULL;
|
|
}
|
|
prop->mType = kTypeNone;
|
|
}
|
|
}
|
|
|
|
void MediaAnalyticsItem::copyProp(Prop *dst, const Prop *src)
|
|
{
|
|
// get rid of any pointers in the dst
|
|
clearProp(dst);
|
|
|
|
*dst = *src;
|
|
|
|
// fix any pointers that we blindly copied, so we have our own copies
|
|
if (dst->mName) {
|
|
void *p = malloc(dst->mNameLen + 1);
|
|
memcpy (p, src->mName, dst->mNameLen + 1);
|
|
dst->mName = (const char *) p;
|
|
}
|
|
if (dst->mType == kTypeCString) {
|
|
dst->u.CStringValue = strdup(src->u.CStringValue);
|
|
}
|
|
}
|
|
|
|
void MediaAnalyticsItem::growProps(int increment)
|
|
{
|
|
if (increment <= 0) {
|
|
increment = kGrowProps;
|
|
}
|
|
int nsize = mPropSize + increment;
|
|
Prop *ni = (Prop *)realloc(mProps, sizeof(Prop) * nsize);
|
|
|
|
if (ni != NULL) {
|
|
for (int i = mPropSize; i < nsize; i++) {
|
|
initProp(&ni[i]);
|
|
}
|
|
mProps = ni;
|
|
mPropSize = nsize;
|
|
}
|
|
}
|
|
|
|
// 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::kTypeInt32:
|
|
setInt32(attr, data.readInt32());
|
|
break;
|
|
case MediaAnalyticsItem::kTypeInt64:
|
|
setInt64(attr, data.readInt64());
|
|
break;
|
|
case MediaAnalyticsItem::kTypeDouble:
|
|
setDouble(attr, data.readDouble());
|
|
break;
|
|
case MediaAnalyticsItem::kTypeCString:
|
|
setCString(attr, data.readCString());
|
|
break;
|
|
case MediaAnalyticsItem::kTypeRate:
|
|
{
|
|
int64_t count = data.readInt64();
|
|
int64_t duration = data.readInt64();
|
|
setRate(attr, count, duration);
|
|
}
|
|
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 = mPropCount;
|
|
data->writeInt32(count);
|
|
for (int i = 0 ; i < count; i++ ) {
|
|
Prop *prop = &mProps[i];
|
|
data->writeCString(prop->mName);
|
|
data->writeInt32(prop->mType);
|
|
switch (prop->mType) {
|
|
case MediaAnalyticsItem::kTypeInt32:
|
|
data->writeInt32(prop->u.int32Value);
|
|
break;
|
|
case MediaAnalyticsItem::kTypeInt64:
|
|
data->writeInt64(prop->u.int64Value);
|
|
break;
|
|
case MediaAnalyticsItem::kTypeDouble:
|
|
data->writeDouble(prop->u.doubleValue);
|
|
break;
|
|
case MediaAnalyticsItem::kTypeRate:
|
|
data->writeInt64(prop->u.rate.count);
|
|
data->writeInt64(prop->u.rate.duration);
|
|
break;
|
|
case MediaAnalyticsItem::kTypeCString:
|
|
data->writeCString(prop->u.CStringValue);
|
|
break;
|
|
default:
|
|
ALOGE("found bad Prop type: %d, idx %d, name %s",
|
|
prop->mType, i, prop->mName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
AString MediaAnalyticsItem::toString() {
|
|
|
|
AString result = "(";
|
|
char buffer[512];
|
|
|
|
// same order as we spill into the parcel, although not required
|
|
// key+session are our primary matching criteria
|
|
//RBE ALOGD("mKey.c_str");
|
|
result.append(mKey.c_str());
|
|
//RBE ALOGD("post-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 = mPropCount;
|
|
snprintf(buffer, sizeof(buffer), "%d:", count);
|
|
result.append(buffer);
|
|
for (int i = 0 ; i < count; i++ ) {
|
|
Prop *prop = &mProps[i];
|
|
switch (prop->mType) {
|
|
case MediaAnalyticsItem::kTypeInt32:
|
|
snprintf(buffer,sizeof(buffer),
|
|
"%s=%d:", prop->mName, prop->u.int32Value);
|
|
break;
|
|
case MediaAnalyticsItem::kTypeInt64:
|
|
snprintf(buffer,sizeof(buffer),
|
|
"%s=%" PRId64 ":", prop->mName, prop->u.int64Value);
|
|
break;
|
|
case MediaAnalyticsItem::kTypeDouble:
|
|
snprintf(buffer,sizeof(buffer),
|
|
"%s=%e:", prop->mName, prop->u.doubleValue);
|
|
break;
|
|
case MediaAnalyticsItem::kTypeRate:
|
|
snprintf(buffer,sizeof(buffer),
|
|
"%s=%" PRId64 "/%" PRId64 ":", prop->mName,
|
|
prop->u.rate.count, prop->u.rate.duration);
|
|
break;
|
|
case MediaAnalyticsItem::kTypeCString:
|
|
snprintf(buffer,sizeof(buffer), "%s=", prop->mName);
|
|
result.append(buffer);
|
|
// XXX: sanitize string for ':' '='
|
|
result.append(prop->u.CStringValue);
|
|
buffer[0] = ':';
|
|
buffer[1] = '\0';
|
|
break;
|
|
default:
|
|
ALOGE("to_String bad item type: %d for %s",
|
|
prop->mType, prop->mName);
|
|
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) {
|
|
|
|
if (DEBUG_API) {
|
|
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 {
|
|
AString p = this->toString();
|
|
ALOGD("Unable to record: %s [forcenew=%d]", p.c_str(), forcenew);
|
|
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.metrics";
|
|
static int tries_remaining = SVC_TRIES;
|
|
int enabled = isEnabled();
|
|
|
|
if (enabled == false) {
|
|
if (DEBUG_SERVICEACCESS) {
|
|
ALOGD("disabled");
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// completely skip logging from certain UIDs. We do this here
|
|
// to avoid the multi-second timeouts while we learn that
|
|
// sepolicy will not let us find the service.
|
|
// We do this only for a select set of UIDs
|
|
// The sepolicy protection is still in place, we just want a faster
|
|
// response from this specific, small set of uids.
|
|
{
|
|
uid_t uid = getuid();
|
|
switch (uid) {
|
|
case AID_RADIO: // telephony subsystem, RIL
|
|
return NULL;
|
|
break;
|
|
default:
|
|
// let sepolicy deny access if appropriate
|
|
break;
|
|
}
|
|
}
|
|
|
|
{
|
|
Mutex::Autolock _l(sInitMutex);
|
|
const char *badness = "";
|
|
|
|
// think of tries_remaining as telling us whether service==NULL because
|
|
// (1) we haven't tried to initialize it yet
|
|
// (2) we've tried to initialize it, but failed.
|
|
if (sAnalyticsService == NULL && tries_remaining > 0) {
|
|
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";
|
|
}
|
|
|
|
if (sAnalyticsService == NULL) {
|
|
if (tries_remaining > 0) {
|
|
tries_remaining--;
|
|
}
|
|
if (DEBUG_SERVICEACCESS) {
|
|
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(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->mPropCount;
|
|
for (int i = 0 ; i < nattr; i++ ) {
|
|
Prop *iprop = &incoming->mProps[i];
|
|
Prop *oprop = findProp(iprop->mName);
|
|
const char *p = iprop->mName;
|
|
size_t len = strlen(p);
|
|
char semantic = p[len-1];
|
|
|
|
if (oprop == NULL) {
|
|
// no oprop, so we insert the new one
|
|
oprop = allocateProp(p);
|
|
copyProp(oprop, iprop);
|
|
} else {
|
|
// merge iprop into oprop
|
|
switch (semantic) {
|
|
case '<': // first aka keep old)
|
|
/* nop */
|
|
break;
|
|
|
|
default: // default is 'last'
|
|
case '>': // last (aka keep new)
|
|
copyProp(oprop, iprop);
|
|
break;
|
|
|
|
case '+': /* sum */
|
|
// XXX validate numeric types, sum in place
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// not sure when we'd return false...
|
|
return true;
|
|
}
|
|
|
|
} // namespace android
|
|
|