change media analytics references to media metrics
part 1: make directory names coherent.
Bug: 145780674
Test: build
Test: atest mediametrics_test
Test: atest CtsNativeMediaMetricsTestCases
Change-Id: If552a742df02206f703801a6530b66fe5addac53
diff --git a/services/mediametrics/Android.bp b/services/mediametrics/Android.bp
new file mode 100644
index 0000000..0840f31
--- /dev/null
+++ b/services/mediametrics/Android.bp
@@ -0,0 +1,73 @@
+// Media Statistics service
+//
+
+cc_binary {
+ name: "mediametrics",
+
+ srcs: [
+ "main_mediametrics.cpp",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "liblog",
+ "libmediametricsservice",
+ "libutils",
+ ],
+
+ init_rc: [
+ "mediametrics.rc",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+}
+
+cc_library_shared {
+ name: "libmediametricsservice",
+
+ srcs: [
+ "AudioAnalytics.cpp",
+ "iface_statsd.cpp",
+ "MediaMetricsService.cpp",
+ "statsd_audiopolicy.cpp",
+ "statsd_audiorecord.cpp",
+ "statsd_audiothread.cpp",
+ "statsd_audiotrack.cpp",
+ "statsd_codec.cpp",
+ "statsd_drm.cpp",
+ "statsd_extractor.cpp",
+ "statsd_nuplayer.cpp",
+ "statsd_recorder.cpp",
+ ],
+
+ proto: {
+ type: "lite",
+ },
+
+ shared_libs: [
+ "libbinder",
+ "liblog",
+ "libmediametrics",
+ "libprotobuf-cpp-lite",
+ "libstatslog",
+ "libutils",
+ ],
+
+ static_libs: [
+ "libplatformprotos",
+ ],
+
+ include_dirs: [
+ "system/media/audio_utils/include",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+}
diff --git a/services/mediametrics/AudioAnalytics.cpp b/services/mediametrics/AudioAnalytics.cpp
new file mode 100644
index 0000000..638c4ab
--- /dev/null
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "AudioAnalytics"
+#include <utils/Log.h>
+
+#include "AudioAnalytics.h"
+
+#include <audio_utils/clock.h> // clock conversions
+
+namespace android::mediametrics {
+
+AudioAnalytics::AudioAnalytics()
+{
+ ALOGD("%s", __func__);
+}
+
+AudioAnalytics::~AudioAnalytics()
+{
+ ALOGD("%s", __func__);
+}
+
+status_t AudioAnalytics::submit(
+ const std::shared_ptr<const MediaAnalyticsItem>& item, bool isTrusted)
+{
+ if (startsWith(item->getKey(), "audio.")) {
+ return mTimeMachine.put(item, isTrusted)
+ ?: mTransactionLog.put(item);
+ }
+ return BAD_VALUE;
+}
+
+std::pair<std::string, int32_t> AudioAnalytics::dump(int32_t lines) const
+{
+ std::stringstream ss;
+ int32_t ll = lines;
+
+ if (ll > 0) {
+ ss << "TransactionLog:\n";
+ --ll;
+ }
+ if (ll > 0) {
+ auto [s, l] = mTransactionLog.dump(ll);
+ ss << s;
+ ll -= l;
+ }
+ if (ll > 0) {
+ ss << "TimeMachine:\n";
+ --ll;
+ }
+ if (ll > 0) {
+ auto [s, l] = mTimeMachine.dump(ll);
+ ss << s;
+ ll -= l;
+ }
+ return { ss.str(), lines - ll };
+}
+
+} // namespace android
diff --git a/services/mediametrics/AudioAnalytics.h b/services/mediametrics/AudioAnalytics.h
new file mode 100644
index 0000000..366a809
--- /dev/null
+++ b/services/mediametrics/AudioAnalytics.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include "TimeMachine.h"
+#include "TransactionLog.h"
+
+namespace android::mediametrics {
+
+class AudioAnalytics
+{
+public:
+ AudioAnalytics();
+ ~AudioAnalytics();
+
+ // TODO: update with conditions for keys.
+ /**
+ * Returns success if AudioAnalytics recognizes item.
+ *
+ * AudioAnalytics requires the item key to start with "audio.".
+ *
+ * A trusted source can create a new key, an untrusted source
+ * can only modify the key if the uid will match that authorized
+ * on the existing key.
+ *
+ * \param item the item to be submitted.
+ * \param isTrusted whether the transaction comes from a trusted source.
+ * In this case, a trusted source is verified by binder
+ * UID to be a system service by MediaMetrics service.
+ * Do not use true if you haven't really checked!
+ */
+ status_t submit(const std::shared_ptr<const MediaAnalyticsItem>& item, bool isTrusted);
+
+ /**
+ * Returns a pair consisting of the dump string, and the number of lines in the string.
+ *
+ * The number of lines in the returned pair is used as an optimization
+ * for subsequent line limiting.
+ *
+ * The TimeMachine and the TransactionLog are dumped separately under
+ * different locks, so may not be 100% consistent with the last data
+ * delivered.
+ *
+ * \param lines the maximum number of lines in the string returned.
+ */
+ std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const;
+
+private:
+ // The following are locked internally
+ TimeMachine mTimeMachine;
+ TransactionLog mTransactionLog;
+};
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/MediaMetricsService.cpp b/services/mediametrics/MediaMetricsService.cpp
new file mode 100644
index 0000000..b735b81
--- /dev/null
+++ b/services/mediametrics/MediaMetricsService.cpp
@@ -0,0 +1,599 @@
+/*
+ * Copyright (C) 2017 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_NDEBUG 0
+#define LOG_TAG "MediaAnalyticsService"
+#include <utils/Log.h>
+
+#include "MediaMetricsService.h"
+
+#include <pwd.h> //getpwuid
+
+#include <android/content/pm/IPackageManagerNative.h> // package info
+#include <audio_utils/clock.h> // clock conversions
+#include <binder/IPCThreadState.h> // get calling uid
+#include <cutils/properties.h> // for property_get
+#include <private/android_filesystem_config.h> // UID
+
+namespace android {
+
+using namespace mediametrics;
+
+// individual records kept in memory: age or count
+// age: <= 28 hours (1 1/6 days)
+// count: hard limit of # records
+// (0 for either of these disables that threshold)
+//
+static constexpr nsecs_t kMaxRecordAgeNs = 28 * 3600 * NANOS_PER_SECOND;
+// 2019/6: average daily per device is currently 375-ish;
+// setting this to 2000 is large enough to catch most devices
+// we'll lose some data on very very media-active devices, but only for
+// the gms collection; statsd will have already covered those for us.
+// This also retains enough information to help with bugreports
+static constexpr size_t kMaxRecords = 2000;
+
+// max we expire in a single call, to constrain how long we hold the
+// mutex, which also constrains how long a client might wait.
+static constexpr size_t kMaxExpiredAtOnce = 50;
+
+// TODO: need to look at tuning kMaxRecords and friends for low-memory devices
+
+/* static */
+nsecs_t MediaAnalyticsService::roundTime(nsecs_t timeNs)
+{
+ return (timeNs + NANOS_PER_SECOND / 2) / NANOS_PER_SECOND * NANOS_PER_SECOND;
+}
+
+MediaAnalyticsService::MediaAnalyticsService()
+ : mMaxRecords(kMaxRecords),
+ mMaxRecordAgeNs(kMaxRecordAgeNs),
+ mMaxRecordsExpiredAtOnce(kMaxExpiredAtOnce),
+ mDumpProtoDefault(MediaAnalyticsItem::PROTO_V1)
+{
+ ALOGD("%s", __func__);
+}
+
+MediaAnalyticsService::~MediaAnalyticsService()
+{
+ ALOGD("%s", __func__);
+ // the class destructor clears anyhow, but we enforce clearing items first.
+ mItemsDiscarded += mItems.size();
+ mItems.clear();
+}
+
+status_t MediaAnalyticsService::submitInternal(MediaAnalyticsItem *item, bool release)
+{
+ // calling PID is 0 for one-way calls.
+ const pid_t pid = IPCThreadState::self()->getCallingPid();
+ const pid_t pid_given = item->getPid();
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
+ const uid_t uid_given = item->getUid();
+
+ //ALOGD("%s: caller pid=%d uid=%d, item pid=%d uid=%d", __func__,
+ // (int)pid, (int)uid, (int) pid_given, (int)uid_given);
+
+ bool isTrusted;
+ switch (uid) {
+ case AID_AUDIOSERVER:
+ case AID_BLUETOOTH:
+ case AID_CAMERA:
+ case AID_DRM:
+ case AID_MEDIA:
+ case AID_MEDIA_CODEC:
+ case AID_MEDIA_EX:
+ case AID_MEDIA_DRM:
+ case AID_SYSTEM:
+ // trusted source, only override default values
+ isTrusted = true;
+ if (uid_given == (uid_t)-1) {
+ item->setUid(uid);
+ }
+ if (pid_given == (pid_t)-1) {
+ item->setPid(pid); // if one-way then this is 0.
+ }
+ break;
+ default:
+ isTrusted = false;
+ item->setPid(pid); // always use calling pid, if one-way then this is 0.
+ item->setUid(uid);
+ break;
+ }
+
+ // Overwrite package name and version if the caller was untrusted.
+ if (!isTrusted) {
+ mUidInfo.setPkgInfo(item, item->getUid(), true, true);
+ } else if (item->getPkgName().empty()) {
+ // empty, so fill out both parts
+ mUidInfo.setPkgInfo(item, item->getUid(), true, true);
+ } else {
+ // trusted, provided a package, do nothing
+ }
+
+ ALOGV("%s: given uid %d; sanitized uid: %d sanitized pkg: %s "
+ "sanitized pkg version: %lld",
+ __func__,
+ uid_given, item->getUid(),
+ item->getPkgName().c_str(),
+ (long long)item->getPkgVersionCode());
+
+ mItemsSubmitted++;
+
+ // validate the record; we discard if we don't like it
+ if (isContentValid(item, isTrusted) == false) {
+ if (release) delete item;
+ return PERMISSION_DENIED;
+ }
+
+ // XXX: if we have a sessionid in the new record, look to make
+ // sure it doesn't appear in the finalized list.
+
+ if (item->count() == 0) {
+ ALOGV("%s: dropping empty record...", __func__);
+ if (release) delete item;
+ return BAD_VALUE;
+ }
+
+ if (!isTrusted || item->getTimestamp() == 0) {
+ // WestWorld logs two times for events: ElapsedRealTimeNs (BOOTTIME) and
+ // WallClockTimeNs (REALTIME). The new audio keys use BOOTTIME.
+ //
+ // TODO: Reevaluate time base with other teams.
+ const bool useBootTime = startsWith(item->getKey(), "audio.");
+ const int64_t now = systemTime(useBootTime ? SYSTEM_TIME_BOOTTIME : SYSTEM_TIME_REALTIME);
+ item->setTimestamp(now);
+ }
+
+ // now attach either the item or its dup to a const shared pointer
+ std::shared_ptr<const MediaAnalyticsItem> sitem(release ? item : item->dup());
+
+ (void)mAudioAnalytics.submit(sitem, isTrusted);
+
+ extern bool dump2Statsd(const std::shared_ptr<const MediaAnalyticsItem>& item);
+ (void)dump2Statsd(sitem); // failure should be logged in function.
+ saveItem(sitem);
+ return NO_ERROR;
+}
+
+status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
+{
+ String8 result;
+
+ if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+ result.appendFormat("Permission Denial: "
+ "can't dump MediaAnalyticsService from pid=%d, uid=%d\n",
+ IPCThreadState::self()->getCallingPid(),
+ IPCThreadState::self()->getCallingUid());
+ write(fd, result.string(), result.size());
+ return NO_ERROR;
+ }
+
+ // crack any parameters
+ const String16 protoOption("-proto");
+ int chosenProto = mDumpProtoDefault;
+ const String16 clearOption("-clear");
+ bool clear = false;
+ const String16 sinceOption("-since");
+ nsecs_t ts_since = 0;
+ const String16 helpOption("-help");
+ const String16 onlyOption("-only");
+ std::string only;
+ const int n = args.size();
+ for (int i = 0; i < n; i++) {
+ if (args[i] == clearOption) {
+ clear = true;
+ } else if (args[i] == protoOption) {
+ i++;
+ if (i < n) {
+ String8 value(args[i]);
+ int proto = MediaAnalyticsItem::PROTO_V0;
+ char *endp;
+ const char *p = value.string();
+ proto = strtol(p, &endp, 10);
+ if (endp != p || *endp == '\0') {
+ if (proto < MediaAnalyticsItem::PROTO_FIRST) {
+ proto = MediaAnalyticsItem::PROTO_FIRST;
+ } else if (proto > MediaAnalyticsItem::PROTO_LAST) {
+ proto = MediaAnalyticsItem::PROTO_LAST;
+ }
+ chosenProto = proto;
+ } else {
+ result.append("unable to parse value for -proto\n\n");
+ }
+ } else {
+ result.append("missing value for -proto\n\n");
+ }
+ } else if (args[i] == sinceOption) {
+ i++;
+ if (i < n) {
+ String8 value(args[i]);
+ char *endp;
+ const char *p = value.string();
+ ts_since = strtoll(p, &endp, 10);
+ if (endp == p || *endp != '\0') {
+ ts_since = 0;
+ }
+ } else {
+ ts_since = 0;
+ }
+ // command line is milliseconds; internal units are nano-seconds
+ ts_since *= NANOS_PER_MILLISECOND;
+ } else if (args[i] == onlyOption) {
+ i++;
+ if (i < n) {
+ String8 value(args[i]);
+ only = value.string();
+ }
+ } else if (args[i] == helpOption) {
+ // TODO: consider function area dumping.
+ // dumpsys media.metrics audiotrack,codec
+ // or dumpsys media.metrics audiotrack codec
+
+ result.append("Recognized parameters:\n");
+ result.append("-help this help message\n");
+ result.append("-proto # dump using protocol #");
+ result.append("-clear clears out saved records\n");
+ result.append("-only X process records for component X\n");
+ result.append("-since X include records since X\n");
+ result.append(" (X is milliseconds since the UNIX epoch)\n");
+ write(fd, result.string(), result.size());
+ return NO_ERROR;
+ }
+ }
+
+ {
+ std::lock_guard _l(mLock);
+
+ result.appendFormat("Dump of the %s process:\n", kServiceName);
+ dumpHeaders_l(result, chosenProto, ts_since);
+ dumpRecent_l(result, chosenProto, ts_since, only.c_str());
+
+ if (clear) {
+ mItemsDiscarded += mItems.size();
+ mItems.clear();
+ // shall we clear the summary data too?
+ }
+ // TODO: maybe consider a better way of dumping audio analytics info.
+ constexpr int32_t linesToDump = 1000;
+ result.append(mAudioAnalytics.dump(linesToDump).first.c_str());
+ }
+
+ write(fd, result.string(), result.size());
+ return NO_ERROR;
+}
+
+// dump headers
+void MediaAnalyticsService::dumpHeaders_l(String8 &result, int dumpProto, nsecs_t ts_since)
+{
+ result.appendFormat("Protocol Version: %d\n", dumpProto);
+ if (MediaAnalyticsItem::isEnabled()) {
+ result.append("Metrics gathering: enabled\n");
+ } else {
+ result.append("Metrics gathering: DISABLED via property\n");
+ }
+ result.appendFormat(
+ "Since Boot: Submissions: %lld Accepted: %lld\n",
+ (long long)mItemsSubmitted.load(), (long long)mItemsFinalized);
+ result.appendFormat(
+ "Records Discarded: %lld (by Count: %lld by Expiration: %lld)\n",
+ (long long)mItemsDiscarded, (long long)mItemsDiscardedCount,
+ (long long)mItemsDiscardedExpire);
+ if (ts_since != 0) {
+ result.appendFormat(
+ "Emitting Queue entries more recent than: %lld\n",
+ (long long)ts_since);
+ }
+}
+
+void MediaAnalyticsService::dumpRecent_l(
+ String8 &result, int dumpProto, nsecs_t ts_since, const char * only)
+{
+ if (only != nullptr && *only == '\0') {
+ only = nullptr;
+ }
+ result.append("\nFinalized Metrics (oldest first):\n");
+ dumpQueue_l(result, dumpProto, ts_since, only);
+
+ // 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
+}
+
+void MediaAnalyticsService::dumpQueue_l(String8 &result, int dumpProto) {
+ dumpQueue_l(result, dumpProto, (nsecs_t) 0, nullptr /* only */);
+}
+
+void MediaAnalyticsService::dumpQueue_l(
+ String8 &result, int dumpProto, nsecs_t ts_since, const char * only) {
+ int slot = 0;
+
+ if (mItems.empty()) {
+ result.append("empty\n");
+ } else {
+ for (const auto &item : mItems) {
+ nsecs_t when = item->getTimestamp();
+ if (when < ts_since) {
+ continue;
+ }
+ // TODO: Only should be a set<string>
+ if (only != nullptr &&
+ item->getKey() /* std::string */ != only) {
+ ALOGV("%s: omit '%s', it's not '%s'",
+ __func__, item->getKey().c_str(), only);
+ continue;
+ }
+ result.appendFormat("%5d: %s\n",
+ slot, item->toString(dumpProto).c_str());
+ slot++;
+ }
+ }
+}
+
+//
+// Our Cheap in-core, non-persistent records management.
+
+// if item != NULL, it's the item we just inserted
+// true == more items eligible to be recovered
+bool MediaAnalyticsService::expirations_l(const std::shared_ptr<const MediaAnalyticsItem>& item)
+{
+ bool more = false;
+
+ // check queue size
+ size_t overlimit = 0;
+ if (mMaxRecords > 0 && mItems.size() > mMaxRecords) {
+ overlimit = mItems.size() - mMaxRecords;
+ if (overlimit > mMaxRecordsExpiredAtOnce) {
+ more = true;
+ overlimit = mMaxRecordsExpiredAtOnce;
+ }
+ }
+
+ // check queue times
+ size_t expired = 0;
+ if (!more && mMaxRecordAgeNs > 0) {
+ const nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+ // we check one at a time, skip search would be more efficient.
+ size_t i = overlimit;
+ for (; i < mItems.size(); ++i) {
+ auto &oitem = mItems[i];
+ nsecs_t when = oitem->getTimestamp();
+ if (oitem.get() == item.get()) {
+ break;
+ }
+ if (now > when && (now - when) <= mMaxRecordAgeNs) {
+ break; // TODO: if we use BOOTTIME, should be monotonic.
+ }
+ if (i >= mMaxRecordsExpiredAtOnce) {
+ // this represents "one too many"; tell caller there are
+ // more to be reclaimed.
+ more = true;
+ break;
+ }
+ }
+ expired = i - overlimit;
+ }
+
+ if (const size_t toErase = overlimit + expired;
+ toErase > 0) {
+ mItemsDiscardedCount += overlimit;
+ mItemsDiscardedExpire += expired;
+ mItemsDiscarded += toErase;
+ mItems.erase(mItems.begin(), mItems.begin() + toErase); // erase from front
+ }
+ return more;
+}
+
+void MediaAnalyticsService::processExpirations()
+{
+ bool more;
+ do {
+ sleep(1);
+ std::lock_guard _l(mLock);
+ more = expirations_l(nullptr);
+ } while (more);
+}
+
+void MediaAnalyticsService::saveItem(const std::shared_ptr<const MediaAnalyticsItem>& item)
+{
+ std::lock_guard _l(mLock);
+ // we assume the items are roughly in time order.
+ mItems.emplace_back(item);
+ ++mItemsFinalized;
+ if (expirations_l(item)
+ && (!mExpireFuture.valid()
+ || mExpireFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready)) {
+ mExpireFuture = std::async(std::launch::async, [this] { processExpirations(); });
+ }
+}
+
+/* static */
+bool MediaAnalyticsService::isContentValid(const MediaAnalyticsItem *item, bool isTrusted)
+{
+ if (isTrusted) return true;
+ // untrusted uids can only send us a limited set of keys
+ const std::string &key = item->getKey();
+ if (startsWith(key, "audio.")) return true;
+ for (const char *allowedKey : {
+ // legacy audio
+ "audiopolicy",
+ "audiorecord",
+ "audiothread",
+ "audiotrack",
+ // other media
+ "codec",
+ "extractor",
+ "nuplayer",
+ }) {
+ if (key == allowedKey) {
+ return true;
+ }
+ }
+ ALOGD("%s: invalid key: %s", __func__, item->toString().c_str());
+ return false;
+}
+
+// are we rate limited, normally false
+bool MediaAnalyticsService::isRateLimited(MediaAnalyticsItem *) const
+{
+ return false;
+}
+
+// How long we hold package info before we re-fetch it
+constexpr nsecs_t PKG_EXPIRATION_NS = 30 * 60 * NANOS_PER_SECOND; // 30 minutes
+
+// give me the package name, perhaps going to find it
+// manages its own mutex operations internally
+void MediaAnalyticsService::UidInfo::setPkgInfo(
+ MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion)
+{
+ ALOGV("%s: uid=%d", __func__, uid);
+
+ if (!setName && !setVersion) {
+ return; // setting nothing? strange
+ }
+
+ const nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+ struct UidToPkgInfo mapping;
+ {
+ std::lock_guard _l(mUidInfoLock);
+ auto it = mPkgMappings.find(uid);
+ if (it != mPkgMappings.end()) {
+ mapping = it->second;
+ ALOGV("%s: uid %d expiration %lld now %lld",
+ __func__, uid, (long long)mapping.expiration, (long long)now);
+ if (mapping.expiration <= now) {
+ // purge the stale entry and fall into re-fetching
+ ALOGV("%s: entry for uid %d expired, now %lld",
+ __func__, uid, (long long)now);
+ mPkgMappings.erase(it);
+ mapping.uid = (uid_t)-1; // this is always fully overwritten
+ }
+ }
+ }
+
+ // if we did not find it
+ if (mapping.uid == (uid_t)(-1)) {
+ std::string pkg;
+ std::string installer;
+ int64_t versionCode = 0;
+
+ const struct passwd *pw = getpwuid(uid);
+ if (pw) {
+ pkg = pw->pw_name;
+ }
+
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<content::pm::IPackageManagerNative> package_mgr;
+ if (sm.get() == nullptr) {
+ ALOGE("%s: Cannot find service manager", __func__);
+ } else {
+ sp<IBinder> binder = sm->getService(String16("package_native"));
+ if (binder.get() == nullptr) {
+ ALOGE("%s: Cannot find package_native", __func__);
+ } else {
+ package_mgr = interface_cast<content::pm::IPackageManagerNative>(binder);
+ }
+ }
+
+ if (package_mgr != nullptr) {
+ std::vector<int> uids;
+ std::vector<std::string> names;
+ uids.push_back(uid);
+ binder::Status status = package_mgr->getNamesForUids(uids, &names);
+ if (!status.isOk()) {
+ ALOGE("%s: getNamesForUids failed: %s",
+ __func__, status.exceptionMessage().c_str());
+ } else {
+ if (!names[0].empty()) {
+ pkg = names[0].c_str();
+ }
+ }
+ }
+
+ // strip any leading "shared:" strings that came back
+ if (pkg.compare(0, 7, "shared:") == 0) {
+ pkg.erase(0, 7);
+ }
+ // determine how pkg was installed and the versionCode
+ if (pkg.empty()) {
+ pkg = std::to_string(uid); // no name for us to manage
+ } else if (strchr(pkg.c_str(), '.') == NULL) {
+ // not of form 'com.whatever...'; assume internal and ok
+ } else if (strncmp(pkg.c_str(), "android.", 8) == 0) {
+ // android.* packages are assumed fine
+ } else if (package_mgr.get() != nullptr) {
+ String16 pkgName16(pkg.c_str());
+ binder::Status status = package_mgr->getInstallerForPackage(pkgName16, &installer);
+ if (!status.isOk()) {
+ ALOGE("%s: getInstallerForPackage failed: %s",
+ __func__, status.exceptionMessage().c_str());
+ }
+
+ // skip if we didn't get an installer
+ if (status.isOk()) {
+ status = package_mgr->getVersionCodeForPackage(pkgName16, &versionCode);
+ if (!status.isOk()) {
+ ALOGE("%s: getVersionCodeForPackage failed: %s",
+ __func__, status.exceptionMessage().c_str());
+ }
+ }
+
+ ALOGV("%s: package '%s' installed by '%s' versioncode %lld",
+ __func__, pkg.c_str(), installer.c_str(), (long long)versionCode);
+
+ if (strncmp(installer.c_str(), "com.android.", 12) == 0) {
+ // from play store, we keep info
+ } else if (strncmp(installer.c_str(), "com.google.", 11) == 0) {
+ // some google source, we keep info
+ } else if (strcmp(installer.c_str(), "preload") == 0) {
+ // preloads, we keep the info
+ } else if (installer.c_str()[0] == '\0') {
+ // sideload (no installer); report UID only
+ pkg = std::to_string(uid);
+ versionCode = 0;
+ } else {
+ // unknown installer; report UID only
+ pkg = std::to_string(uid);
+ versionCode = 0;
+ }
+ } else {
+ // unvalidated by package_mgr just send uid.
+ pkg = std::to_string(uid);
+ }
+
+ // add it to the map, to save a subsequent lookup
+ std::lock_guard _l(mUidInfoLock);
+ // always overwrite
+ mapping.uid = uid;
+ mapping.pkg = std::move(pkg);
+ mapping.installer = std::move(installer);
+ mapping.versionCode = versionCode;
+ mapping.expiration = now + PKG_EXPIRATION_NS;
+ ALOGV("%s: adding uid %d pkg '%s' expiration: %lld",
+ __func__, uid, mapping.pkg.c_str(), (long long)mapping.expiration);
+ mPkgMappings[uid] = mapping;
+ }
+
+ if (mapping.uid != (uid_t)(-1)) {
+ if (setName) {
+ item->setPkgName(mapping.pkg);
+ }
+ if (setVersion) {
+ item->setPkgVersionCode(mapping.versionCode);
+ }
+ }
+}
+
+} // namespace android
diff --git a/services/mediametrics/MediaMetricsService.h b/services/mediametrics/MediaMetricsService.h
new file mode 100644
index 0000000..5bdc48f
--- /dev/null
+++ b/services/mediametrics/MediaMetricsService.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <deque>
+#include <future>
+#include <mutex>
+#include <unordered_map>
+
+// IMediaAnalyticsService must include Vector, String16, Errors
+#include <media/IMediaAnalyticsService.h>
+#include <utils/String8.h>
+
+#include "AudioAnalytics.h"
+
+namespace android {
+
+class MediaAnalyticsService : public BnMediaAnalyticsService
+{
+public:
+ MediaAnalyticsService();
+ ~MediaAnalyticsService() override;
+
+ /**
+ * Submits the indicated record to the mediaanalytics service.
+ *
+ * \param item the item to submit.
+ * \return status failure, which is negative on binder transaction failure.
+ * As the transaction is one-way, remote failures will not be reported.
+ */
+ status_t submit(MediaAnalyticsItem *item) override {
+ return submitInternal(item, false /* release */);
+ }
+
+ status_t submitBuffer(const char *buffer, size_t length) override {
+ MediaAnalyticsItem *item = new MediaAnalyticsItem();
+ return item->readFromByteString(buffer, length)
+ ?: submitInternal(item, true /* release */);
+ }
+
+ status_t dump(int fd, const Vector<String16>& args) override;
+
+ static constexpr const char * const kServiceName = "media.metrics";
+
+ /**
+ * Rounds time to the nearest second.
+ */
+ static nsecs_t roundTime(nsecs_t timeNs);
+
+protected:
+
+ // Internal call where release is true if ownership of item is transferred
+ // to the service (that is, the service will eventually delete the item).
+ status_t submitInternal(MediaAnalyticsItem *item, bool release) override;
+
+private:
+ void processExpirations();
+ // input validation after arrival from client
+ static bool isContentValid(const MediaAnalyticsItem *item, bool isTrusted);
+ bool isRateLimited(MediaAnalyticsItem *) const;
+ void saveItem(const std::shared_ptr<const MediaAnalyticsItem>& item);
+
+ // The following methods are GUARDED_BY(mLock)
+ bool expirations_l(const std::shared_ptr<const MediaAnalyticsItem>& item);
+
+ // support for generating output
+ void dumpQueue_l(String8 &result, int dumpProto);
+ void dumpQueue_l(String8 &result, int dumpProto, nsecs_t, const char *only);
+ void dumpHeaders_l(String8 &result, int dumpProto, nsecs_t ts_since);
+ void dumpSummaries_l(String8 &result, int dumpProto, nsecs_t ts_since, const char * only);
+ void dumpRecent_l(String8 &result, int dumpProto, nsecs_t ts_since, const char * only);
+
+ // The following variables accessed without mLock
+
+ // limit how many records we'll retain
+ // by count (in each queue (open, finalized))
+ const size_t mMaxRecords;
+ // by time (none older than this)
+ const nsecs_t mMaxRecordAgeNs;
+ // max to expire per expirations_l() invocation
+ const size_t mMaxRecordsExpiredAtOnce;
+ const int mDumpProtoDefault;
+
+ class UidInfo {
+ public:
+ void setPkgInfo(MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion);
+
+ private:
+ std::mutex mUidInfoLock;
+
+ struct UidToPkgInfo {
+ uid_t uid = -1;
+ std::string pkg;
+ std::string installer;
+ int64_t versionCode = 0;
+ nsecs_t expiration = 0; // TODO: remove expiration.
+ };
+
+ // TODO: use concurrent hashmap with striped lock.
+ std::unordered_map<uid_t, struct UidToPkgInfo> mPkgMappings; // GUARDED_BY(mUidInfoLock)
+ } mUidInfo; // mUidInfo can be accessed without lock (locked internally)
+
+ std::atomic<int64_t> mItemsSubmitted{}; // accessed outside of lock.
+
+ mediametrics::AudioAnalytics mAudioAnalytics;
+
+ std::mutex mLock;
+ // statistics about our analytics
+ int64_t mItemsFinalized = 0; // GUARDED_BY(mLock)
+ int64_t mItemsDiscarded = 0; // GUARDED_BY(mLock)
+ int64_t mItemsDiscardedExpire = 0; // GUARDED_BY(mLock)
+ int64_t mItemsDiscardedCount = 0; // GUARDED_BY(mLock)
+
+ // If we have a worker thread to garbage collect
+ std::future<void> mExpireFuture; // GUARDED_BY(mLock)
+
+ // Our item queue, generally (oldest at front)
+ // TODO: Make separate class, use segmented queue, write lock only end.
+ // Note: Another analytics module might have ownership of an item longer than the log.
+ std::deque<std::shared_ptr<const MediaAnalyticsItem>> mItems; // GUARDED_BY(mLock)
+};
+
+} // namespace android
diff --git a/services/mediametrics/OWNERS b/services/mediametrics/OWNERS
new file mode 100644
index 0000000..e37a1f8
--- /dev/null
+++ b/services/mediametrics/OWNERS
@@ -0,0 +1,2 @@
+essick@google.com
+hunga@google.com
diff --git a/services/mediametrics/TEST_MAPPING b/services/mediametrics/TEST_MAPPING
new file mode 100644
index 0000000..01e9e46
--- /dev/null
+++ b/services/mediametrics/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+ "presubmit": [
+ {
+ "name": "mediametrics_tests"
+ },
+ {
+ "name": "CtsNativeMediaMetricsTestCases"
+ }
+ ]
+}
diff --git a/services/mediametrics/TimeMachine.h b/services/mediametrics/TimeMachine.h
new file mode 100644
index 0000000..578b838
--- /dev/null
+++ b/services/mediametrics/TimeMachine.h
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <any>
+#include <map>
+#include <sstream>
+#include <string>
+#include <variant>
+#include <vector>
+
+#include <media/MediaAnalyticsItem.h>
+#include <utils/Timers.h>
+
+namespace android::mediametrics {
+
+// define a way of printing the monostate
+inline std::ostream & operator<< (std::ostream& s,
+ std::monostate const& v __unused) {
+ s << "none_item";
+ return s;
+}
+
+// define a way of printing a variant
+// see https://en.cppreference.com/w/cpp/utility/variant/visit
+template <typename T0, typename ... Ts>
+std::ostream & operator<< (std::ostream& s,
+ std::variant<T0, Ts...> const& v) {
+ std::visit([&s](auto && arg){ s << std::forward<decltype(arg)>(arg); }, v);
+ return s;
+}
+
+/**
+ * The TimeMachine is used to record timing changes of MediaAnalyticItem
+ * properties.
+ *
+ * Any URL that ends with '!' will have a time sequence that keeps duplicates.
+ *
+ * The TimeMachine is NOT thread safe.
+ */
+class TimeMachine {
+
+ using Elem = std::variant<std::monostate, int32_t, int64_t, double, std::string>;
+ using PropertyHistory = std::multimap<int64_t /* time */, Elem>;
+
+ // KeyHistory contains no lock.
+ // Access is through the TimeMachine, and a hash-striped lock is used
+ // before calling into KeyHistory.
+ class KeyHistory {
+ public:
+ template <typename T>
+ KeyHistory(T key, pid_t pid, uid_t uid, int64_t time)
+ : mKey(key)
+ , mPid(pid)
+ , mUid(uid)
+ , mCreationTime(time)
+ , mLastModificationTime(time)
+ {
+ putValue("_pid", (int32_t)pid, time);
+ putValue("_uid", (int32_t)uid, time);
+ }
+
+ status_t checkPermission(uid_t uidCheck) const {
+ return uidCheck != (uid_t)-1 && uidCheck != mUid ? PERMISSION_DENIED : NO_ERROR;
+ }
+
+ template <typename T>
+ status_t getValue(const std::string &property, T* value, int64_t time = 0) const {
+ if (time == 0) time = systemTime(SYSTEM_TIME_BOOTTIME);
+ const auto tsptr = mPropertyMap.find(property);
+ if (tsptr == mPropertyMap.end()) return BAD_VALUE;
+ const auto& timeSequence = tsptr->second;
+ auto eptr = timeSequence.upper_bound(time);
+ if (eptr == timeSequence.begin()) return BAD_VALUE;
+ --eptr;
+ if (eptr == timeSequence.end()) return BAD_VALUE;
+ const T* vptr = std::get_if<T>(&eptr->second);
+ if (vptr == nullptr) return BAD_VALUE;
+ *value = *vptr;
+ return NO_ERROR;
+ }
+
+ template <typename T>
+ status_t getValue(const std::string &property, T defaultValue, int64_t time = 0) const {
+ T value;
+ return getValue(property, &value, time) != NO_ERROR ? defaultValue : value;
+ }
+
+ void putProp(
+ const std::string &name, const MediaAnalyticsItem::Prop &prop, int64_t time = 0) {
+ prop.visit([&](auto value) { putValue(name, value, time); });
+ }
+
+ template <typename T>
+ void putValue(const std::string &property,
+ T&& e, int64_t time = 0) {
+ if (time == 0) time = systemTime(SYSTEM_TIME_BOOTTIME);
+ mLastModificationTime = time;
+ auto& timeSequence = mPropertyMap[property];
+ Elem el{std::forward<T>(e)};
+ if (timeSequence.empty() // no elements
+ || property.back() == '!' // keep duplicates TODO: remove?
+ || timeSequence.rbegin()->second != el) { // value changed
+ timeSequence.emplace(time, std::move(el));
+ }
+ }
+
+ // Explicitly ignore rate properties - we don't expose them for now.
+ void putValue(
+ const std::string &property __unused,
+ std::pair<int64_t, int64_t>& e __unused,
+ int64_t time __unused) {
+ }
+
+ std::pair<std::string, int32_t> dump(int32_t lines, int64_t time) const {
+ std::stringstream ss;
+ int32_t ll = lines;
+ for (auto& tsPair : mPropertyMap) {
+ if (ll <= 0) break;
+ ss << dump(mKey, tsPair, time);
+ --ll;
+ }
+ return { ss.str(), lines - ll };
+ }
+
+ int64_t getLastModificationTime() const { return mLastModificationTime; }
+
+ private:
+ static std::string dump(
+ const std::string &key,
+ const std::pair<std::string /* prop */, PropertyHistory>& tsPair,
+ int64_t time) {
+ const auto timeSequence = tsPair.second;
+ auto eptr = timeSequence.lower_bound(time);
+ if (eptr == timeSequence.end()) {
+ return tsPair.first + "={};\n";
+ }
+ std::stringstream ss;
+ ss << key << "." << tsPair.first << "={";
+ do {
+ ss << eptr->first << ":" << eptr->second << ",";
+ } while (++eptr != timeSequence.end());
+ ss << "};\n";
+ return ss.str();
+ }
+
+ const std::string mKey;
+ const pid_t mPid __unused;
+ const uid_t mUid;
+ const int64_t mCreationTime __unused;
+
+ int64_t mLastModificationTime;
+ std::map<std::string /* property */, PropertyHistory> mPropertyMap;
+ };
+
+ using History = std::map<std::string /* key */, std::shared_ptr<KeyHistory>>;
+
+ static inline constexpr size_t kKeyLowWaterMark = 500;
+ static inline constexpr size_t kKeyHighWaterMark = 1000;
+
+ // Estimated max data space usage is 3KB * kKeyHighWaterMark.
+
+public:
+
+ TimeMachine() = default;
+ TimeMachine(size_t keyLowWaterMark, size_t keyHighWaterMark)
+ : mKeyLowWaterMark(keyLowWaterMark)
+ , mKeyHighWaterMark(keyHighWaterMark) {
+ LOG_ALWAYS_FATAL_IF(keyHighWaterMark <= keyLowWaterMark,
+ "%s: required that keyHighWaterMark:%zu > keyLowWaterMark:%zu",
+ __func__, keyHighWaterMark, keyLowWaterMark);
+ }
+
+ /**
+ * Put all the properties from an item into the Time Machine log.
+ */
+ status_t put(const std::shared_ptr<const MediaAnalyticsItem>& item, bool isTrusted = false) {
+ const int64_t time = item->getTimestamp();
+ const std::string &key = item->getKey();
+
+ std::shared_ptr<KeyHistory> keyHistory;
+ {
+ std::vector<std::any> garbage;
+ std::lock_guard lock(mLock);
+
+ auto it = mHistory.find(key);
+ if (it == mHistory.end()) {
+ if (!isTrusted) return PERMISSION_DENIED;
+
+ (void)gc_l(garbage);
+
+ // no keylock needed here as we are sole owner
+ // until placed on mHistory.
+ keyHistory = std::make_shared<KeyHistory>(
+ key, item->getPid(), item->getUid(), time);
+ mHistory[key] = keyHistory;
+ } else {
+ keyHistory = it->second;
+ }
+ }
+
+ // deferred contains remote properties (for other keys) to do later.
+ std::vector<const MediaAnalyticsItem::Prop *> deferred;
+ {
+ // handle local properties
+ std::lock_guard lock(getLockForKey(key));
+ if (!isTrusted) {
+ status_t status = keyHistory->checkPermission(item->getUid());
+ if (status != NO_ERROR) return status;
+ }
+
+ for (const auto &prop : *item) {
+ const std::string &name = prop.getName();
+ if (name.size() == 0 || name[0] == '_') continue;
+
+ // Cross key settings are with [key]property
+ if (name[0] == '[') {
+ if (!isTrusted) continue;
+ deferred.push_back(&prop);
+ } else {
+ keyHistory->putProp(name, prop, time);
+ }
+ }
+ }
+
+ // handle remote properties, if any
+ for (const auto propptr : deferred) {
+ const auto &prop = *propptr;
+ const std::string &name = prop.getName();
+ size_t end = name.find_first_of(']'); // TODO: handle nested [] or escape?
+ if (end == 0) continue;
+ std::string remoteKey = name.substr(1, end - 1);
+ std::string remoteName = name.substr(end + 1);
+ if (remoteKey.size() == 0 || remoteName.size() == 0) continue;
+ std::shared_ptr<KeyHistory> remoteKeyHistory;
+ {
+ std::lock_guard lock(mLock);
+ auto it = mHistory.find(remoteKey);
+ if (it == mHistory.end()) continue;
+ remoteKeyHistory = it->second;
+ }
+ std::lock_guard(getLockForKey(remoteKey));
+ remoteKeyHistory->putProp(remoteName, prop, time);
+ }
+ return NO_ERROR;
+ }
+
+ template <typename T>
+ status_t get(const std::string &key, const std::string &property,
+ T* value, int32_t uidCheck = -1, int64_t time = 0) const {
+ std::shared_ptr<KeyHistory> keyHistory;
+ {
+ std::lock_guard lock(mLock);
+ const auto it = mHistory.find(key);
+ if (it == mHistory.end()) return BAD_VALUE;
+ keyHistory = it->second;
+ }
+ std::lock_guard lock(getLockForKey(key));
+ return keyHistory->checkPermission(uidCheck)
+ ?: keyHistory->getValue(property, value, time);
+ }
+
+ /**
+ * Individual property put.
+ *
+ * Put takes in a time (if none is provided then BOOTTIME is used).
+ */
+ template <typename T>
+ status_t put(const std::string &url, T &&e, int64_t time = 0) {
+ std::string key;
+ std::string prop;
+ std::shared_ptr<KeyHistory> keyHistory =
+ getKeyHistoryFromUrl(url, &key, &prop);
+ if (keyHistory == nullptr) return BAD_VALUE;
+ if (time == 0) time = systemTime(SYSTEM_TIME_BOOTTIME);
+ std::lock_guard lock(getLockForKey(key));
+ keyHistory->putValue(prop, std::forward<T>(e), time);
+ return NO_ERROR;
+ }
+
+ /**
+ * Individual property get
+ */
+ template <typename T>
+ status_t get(const std::string &url, T* value, int32_t uidCheck, int64_t time = 0) const {
+ std::string key;
+ std::string prop;
+ std::shared_ptr<KeyHistory> keyHistory =
+ getKeyHistoryFromUrl(url, &key, &prop);
+ if (keyHistory == nullptr) return BAD_VALUE;
+
+ std::lock_guard lock(getLockForKey(key));
+ return keyHistory->checkPermission(uidCheck)
+ ?: keyHistory->getValue(prop, value, time);
+ }
+
+ /**
+ * Individual property get with default
+ */
+ template <typename T>
+ T get(const std::string &url, const T &defaultValue, int32_t uidCheck,
+ int64_t time = 0) const {
+ T value;
+ return get(url, &value, uidCheck, time) == NO_ERROR
+ ? value : defaultValue;
+ }
+
+ /**
+ * Returns number of keys in the Time Machine.
+ */
+ size_t size() const {
+ std::lock_guard lock(mLock);
+ return mHistory.size();
+ }
+
+ /**
+ * Clears all properties from the Time Machine.
+ */
+ void clear() {
+ std::lock_guard lock(mLock);
+ mHistory.clear();
+ }
+
+ /**
+ * Returns a pair consisting of the TimeMachine state as a string
+ * and the number of lines in the string.
+ *
+ * The number of lines in the returned pair is used as an optimization
+ * for subsequent line limiting.
+ *
+ * \param lines the maximum number of lines in the string returned.
+ * \param key selects only that key.
+ * \param time to start the dump from.
+ */
+ std::pair<std::string, int32_t> dump(
+ int32_t lines = INT32_MAX, const std::string &key = {}, int64_t time = 0) const {
+ std::lock_guard lock(mLock);
+ if (!key.empty()) { // use std::regex
+ const auto it = mHistory.find(key);
+ if (it == mHistory.end()) return {};
+ std::lock_guard lock(getLockForKey(it->first));
+ return it->second->dump(lines, time);
+ }
+
+ std::stringstream ss;
+ int32_t ll = lines;
+ for (const auto &keyPair : mHistory) {
+ std::lock_guard lock(getLockForKey(keyPair.first));
+ if (lines <= 0) break;
+ auto [s, l] = keyPair.second->dump(ll, time);
+ ss << s;
+ ll -= l;
+ }
+ return { ss.str(), lines - ll };
+ }
+
+private:
+
+ // Obtains the lock for a KeyHistory.
+ std::mutex &getLockForKey(const std::string &key) const {
+ return mKeyLocks[std::hash<std::string>{}(key) % std::size(mKeyLocks)];
+ }
+
+ // Finds a KeyHistory from a URL. Returns nullptr if not found.
+ std::shared_ptr<KeyHistory> getKeyHistoryFromUrl(
+ std::string url, std::string* key, std::string *prop) const {
+ std::lock_guard lock(mLock);
+
+ auto it = mHistory.upper_bound(url);
+ if (it == mHistory.begin()) {
+ return nullptr;
+ }
+ --it; // go to the actual key, if it exists.
+
+ const std::string& itKey = it->first;
+ if (strncmp(itKey.c_str(), url.c_str(), itKey.size())) {
+ return nullptr;
+ }
+ if (key) *key = itKey;
+ if (prop) *prop = url.substr(itKey.size() + 1);
+ return it->second;
+ }
+
+ // GUARDED_BY mLock
+ /**
+ * Garbage collects if the TimeMachine size exceeds the high water mark.
+ *
+ * \param garbage a type-erased vector of elements to be destroyed
+ * outside of lock. Move large items to be destroyed here.
+ *
+ * \return true if garbage collection was done.
+ */
+ bool gc_l(std::vector<std::any>& garbage) {
+ // TODO: something better than this for garbage collection.
+ if (mHistory.size() < mKeyHighWaterMark) return false;
+
+ ALOGD("%s: garbage collection", __func__);
+
+ // erase everything explicitly expired.
+ std::multimap<int64_t, std::string> accessList;
+ // use a stale vector with precise type to avoid type erasure overhead in garbage
+ std::vector<std::shared_ptr<KeyHistory>> stale;
+
+ for (auto it = mHistory.begin(); it != mHistory.end();) {
+ const std::string& key = it->first;
+ std::shared_ptr<KeyHistory> &keyHist = it->second;
+
+ std::lock_guard lock(getLockForKey(it->first));
+ int64_t expireTime = keyHist->getValue("_expire", -1 /* default */);
+ if (expireTime != -1) {
+ stale.emplace_back(std::move(it->second));
+ it = mHistory.erase(it);
+ } else {
+ accessList.emplace(keyHist->getLastModificationTime(), key);
+ ++it;
+ }
+ }
+
+ if (mHistory.size() > mKeyLowWaterMark) {
+ const size_t toDelete = mHistory.size() - mKeyLowWaterMark;
+ auto it = accessList.begin();
+ for (size_t i = 0; i < toDelete; ++i) {
+ auto it2 = mHistory.find(it->second);
+ stale.emplace_back(std::move(it2->second));
+ mHistory.erase(it2);
+ ++it;
+ }
+ }
+ garbage.emplace_back(std::move(accessList));
+ garbage.emplace_back(std::move(stale));
+
+ ALOGD("%s(%zu, %zu): key size:%zu",
+ __func__, mKeyLowWaterMark, mKeyHighWaterMark,
+ mHistory.size());
+ return true;
+ }
+
+ const size_t mKeyLowWaterMark = kKeyLowWaterMark;
+ const size_t mKeyHighWaterMark = kKeyHighWaterMark;
+
+ /**
+ * Locking Strategy
+ *
+ * Each key in the History has a KeyHistory. To get a shared pointer to
+ * the KeyHistory requires a lookup of mHistory under mLock. Once the shared
+ * pointer to KeyHistory is obtained, the mLock for mHistory can be released.
+ *
+ * Once the shared pointer to the key's KeyHistory is obtained, the KeyHistory
+ * can be locked for read and modification through the method getLockForKey().
+ *
+ * Instead of having a mutex per KeyHistory, we use a hash striped lock
+ * which assigns a mutex based on the hash of the key string.
+ *
+ * Once the last shared pointer reference to KeyHistory is released, it is
+ * destroyed. This is done through the garbage collection method.
+ *
+ * This two level locking allows multiple threads to access the TimeMachine
+ * in parallel.
+ */
+
+ mutable std::mutex mLock; // Lock for mHistory
+ History mHistory; // GUARDED_BY mLock
+
+ // KEY_LOCKS is the number of mutexes for keys.
+ // It need not be a power of 2, but faster that way.
+ static inline constexpr size_t KEY_LOCKS = 256;
+ mutable std::mutex mKeyLocks[KEY_LOCKS]; // Hash-striped lock for KeyHistory based on key.
+};
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/TransactionLog.h b/services/mediametrics/TransactionLog.h
new file mode 100644
index 0000000..ca37862
--- /dev/null
+++ b/services/mediametrics/TransactionLog.h
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <any>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include <media/MediaAnalyticsItem.h>
+
+namespace android::mediametrics {
+
+/**
+ * The TransactionLog is used to record MediaAnalyticsItems to present
+ * different views on the time information (selected by audio, and sorted by key).
+ *
+ * The TransactionLog will always present data in timestamp order. (Perhaps we
+ * just make this submit order).
+ *
+ * These Views have a cost in shared pointer storage, so they aren't quite free.
+ *
+ * The TransactionLog is NOT thread safe.
+ */
+class TransactionLog {
+public:
+ // In long term run, the garbage collector aims to keep the
+ // Transaction Log between the Low Water Mark and the High Water Mark.
+
+ // low water mark
+ static inline constexpr size_t kLogItemsLowWater = 5000;
+ // high water mark
+ static inline constexpr size_t kLogItemsHighWater = 10000;
+
+ // Estimated max data usage is 1KB * kLogItemsHighWater.
+
+ TransactionLog() = default;
+
+ TransactionLog(size_t lowWaterMark, size_t highWaterMark)
+ : mLowWaterMark(lowWaterMark)
+ , mHighWaterMark(highWaterMark) {
+ LOG_ALWAYS_FATAL_IF(highWaterMark <= lowWaterMark,
+ "%s: required that highWaterMark:%zu > lowWaterMark:%zu",
+ __func__, highWaterMark, lowWaterMark);
+ }
+
+ /**
+ * Put an item in the TransactionLog.
+ */
+ status_t put(const std::shared_ptr<const MediaAnalyticsItem>& item) {
+ const std::string& key = item->getKey();
+ const int64_t time = item->getTimestamp();
+
+ std::vector<std::any> garbage; // objects destroyed after lock.
+ std::lock_guard lock(mLock);
+
+ (void)gc_l(garbage);
+ mLog.emplace(time, item);
+ mItemMap[key].emplace(time, item);
+ return NO_ERROR; // no errors for now.
+ }
+
+ /**
+ * Returns all records within [startTime, endTime]
+ */
+ std::vector<std::shared_ptr<const MediaAnalyticsItem>> get(
+ int64_t startTime = 0, int64_t endTime = INT64_MAX) const {
+ std::lock_guard lock(mLock);
+ return getItemsInRange_l(mLog, startTime, endTime);
+ }
+
+ /**
+ * Returns all records for a key within [startTime, endTime]
+ */
+ std::vector<std::shared_ptr<const MediaAnalyticsItem>> get(
+ const std::string& key,
+ int64_t startTime = 0, int64_t endTime = INT64_MAX) const {
+ std::lock_guard lock(mLock);
+ auto mapIt = mItemMap.find(key);
+ if (mapIt == mItemMap.end()) return {};
+ return getItemsInRange_l(mapIt->second, startTime, endTime);
+ }
+
+ /**
+ * Returns a pair consisting of the Transaction Log as a string
+ * and the number of lines in the string.
+ *
+ * The number of lines in the returned pair is used as an optimization
+ * for subsequent line limiting.
+ *
+ * \param lines the maximum number of lines in the string returned.
+ */
+ std::pair<std::string, int32_t> dump(int32_t lines) const {
+ std::stringstream ss;
+ int32_t ll = lines;
+ std::lock_guard lock(mLock);
+
+ // All audio items in time order.
+ if (ll > 0) {
+ ss << "Consolidated:\n";
+ --ll;
+ }
+ for (const auto &log : mLog) {
+ if (ll <= 0) break;
+ ss << " " << log.second->toString() << "\n";
+ --ll;
+ }
+
+ // Grouped by item key (category)
+ if (ll > 0) {
+ ss << "Categorized:\n";
+ --ll;
+ }
+ for (const auto &itemMap : mItemMap) {
+ if (ll <= 0) break;
+ ss << " " << itemMap.first << "\n";
+ --ll;
+ for (const auto &item : itemMap.second) {
+ if (ll <= 0) break;
+ ss << " { " << item.first << ", " << item.second->toString() << " }\n";
+ --ll;
+ }
+ }
+ return { ss.str(), lines - ll };
+ }
+
+ /**
+ * Returns number of Items in the TransactionLog.
+ */
+ size_t size() const {
+ std::lock_guard lock(mLock);
+ return mLog.size();
+ }
+
+ /**
+ * Clears all Items from the TransactionLog.
+ */
+ // TODO: Garbage Collector, sweep and expire old values
+ void clear() {
+ std::lock_guard lock(mLock);
+ mLog.clear();
+ mItemMap.clear();
+ }
+
+private:
+ using MapTimeItem =
+ std::multimap<int64_t /* time */, std::shared_ptr<const MediaAnalyticsItem>>;
+
+ // GUARDED_BY mLock
+ /**
+ * Garbage collects if the TimeMachine size exceeds the high water mark.
+ *
+ * \param garbage a type-erased vector of elements to be destroyed
+ * outside of lock. Move large items to be destroyed here.
+ *
+ * \return true if garbage collection was done.
+ */
+ bool gc_l(std::vector<std::any>& garbage) {
+ if (mLog.size() < mHighWaterMark) return false;
+
+ ALOGD("%s: garbage collection", __func__);
+
+ auto eraseEnd = mLog.begin();
+ size_t toRemove = mLog.size() - mLowWaterMark;
+ // remove at least those elements.
+
+ // use a stale vector with precise type to avoid type erasure overhead in garbage
+ std::vector<std::shared_ptr<const MediaAnalyticsItem>> stale;
+
+ for (size_t i = 0; i < toRemove; ++i) {
+ stale.emplace_back(std::move(eraseEnd->second));
+ ++eraseEnd; // amortized O(1)
+ }
+ // ensure that eraseEnd is an lower bound on timeToErase.
+ const int64_t timeToErase = eraseEnd->first;
+ while (eraseEnd != mLog.end()) {
+ auto it = eraseEnd;
+ --it; // amortized O(1)
+ if (it->first != timeToErase) {
+ break; // eraseEnd represents a unique time jump.
+ }
+ stale.emplace_back(std::move(eraseEnd->second));
+ ++eraseEnd;
+ }
+
+ mLog.erase(mLog.begin(), eraseEnd); // O(ptr_diff)
+
+ size_t itemMapCount = 0;
+ for (auto it = mItemMap.begin(); it != mItemMap.end();) {
+ auto &keyHist = it->second;
+ auto it2 = keyHist.lower_bound(timeToErase);
+ if (it2 == keyHist.end()) {
+ garbage.emplace_back(std::move(keyHist)); // directly move keyhist to garbage
+ it = mItemMap.erase(it);
+ } else {
+ for (auto it3 = keyHist.begin(); it3 != it2; ++it3) {
+ stale.emplace_back(std::move(it3->second));
+ }
+ keyHist.erase(keyHist.begin(), it2);
+ itemMapCount += keyHist.size();
+ ++it;
+ }
+ }
+
+ garbage.emplace_back(std::move(stale));
+
+ ALOGD("%s(%zu, %zu): log size:%zu item map size:%zu, item map items:%zu",
+ __func__, mLowWaterMark, mHighWaterMark,
+ mLog.size(), mItemMap.size(), itemMapCount);
+ return true;
+ }
+
+ static std::vector<std::shared_ptr<const MediaAnalyticsItem>> getItemsInRange_l(
+ const MapTimeItem& map,
+ int64_t startTime = 0, int64_t endTime = INT64_MAX) {
+ auto it = map.lower_bound(startTime);
+ if (it == map.end()) return {};
+
+ auto it2 = map.upper_bound(endTime);
+
+ std::vector<std::shared_ptr<const MediaAnalyticsItem>> ret;
+ while (it != it2) {
+ ret.push_back(it->second);
+ ++it;
+ }
+ return ret;
+ }
+
+ const size_t mLowWaterMark = kLogItemsHighWater;
+ const size_t mHighWaterMark = kLogItemsHighWater;
+
+ mutable std::mutex mLock;
+
+ // GUARDED_BY mLock
+ MapTimeItem mLog;
+
+ // GUARDED_BY mLock
+ std::map<std::string /* item_key */, MapTimeItem> mItemMap;
+};
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/iface_statsd.cpp b/services/mediametrics/iface_statsd.cpp
new file mode 100644
index 0000000..962cafe
--- /dev/null
+++ b/services/mediametrics/iface_statsd.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "iface_statsd"
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string.h>
+#include <pwd.h>
+
+#include "MediaMetricsService.h"
+#include "iface_statsd.h"
+
+#include <statslog.h>
+
+namespace android {
+
+// set of routines that crack a MediaAnalyticsItem
+// and send it off to statsd with the appropriate hooks
+//
+// each MediaAnalyticsItem type (extractor, codec, nuplayer, etc)
+// has its own routine to handle this.
+//
+
+bool enabled_statsd = true;
+
+struct statsd_hooks {
+ const char *key;
+ bool (*handler)(const MediaAnalyticsItem *);
+};
+
+// keep this sorted, so we can do binary searches
+static constexpr struct statsd_hooks statsd_handlers[] =
+{
+ { "audiopolicy", statsd_audiopolicy },
+ { "audiorecord", statsd_audiorecord },
+ { "audiothread", statsd_audiothread },
+ { "audiotrack", statsd_audiotrack },
+ { "codec", statsd_codec},
+ { "drm.vendor.Google.WidevineCDM", statsd_widevineCDM },
+ { "drmmanager", statsd_drmmanager },
+ { "extractor", statsd_extractor },
+ { "mediadrm", statsd_mediadrm },
+ { "nuplayer", statsd_nuplayer },
+ { "nuplayer2", statsd_nuplayer },
+ { "recorder", statsd_recorder },
+};
+
+// give me a record, i'll look at the type and upload appropriately
+bool dump2Statsd(const std::shared_ptr<const MediaAnalyticsItem>& item) {
+ if (item == NULL) return false;
+
+ // get the key
+ std::string key = item->getKey();
+
+ if (!enabled_statsd) {
+ ALOGV("statsd logging disabled for record key=%s", key.c_str());
+ return false;
+ }
+
+ for (const auto &statsd_handler : statsd_handlers) {
+ if (key == statsd_handler.key) {
+ return statsd_handler.handler(item.get());
+ }
+ }
+ return false;
+}
+
+} // namespace android
diff --git a/services/mediametrics/iface_statsd.h b/services/mediametrics/iface_statsd.h
new file mode 100644
index 0000000..1c48118
--- /dev/null
+++ b/services/mediametrics/iface_statsd.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+namespace android {
+
+extern bool enabled_statsd;
+
+// component specific dumpers
+extern bool statsd_audiopolicy(const MediaAnalyticsItem *);
+extern bool statsd_audiorecord(const MediaAnalyticsItem *);
+extern bool statsd_audiothread(const MediaAnalyticsItem *);
+extern bool statsd_audiotrack(const MediaAnalyticsItem *);
+extern bool statsd_codec(const MediaAnalyticsItem *);
+extern bool statsd_extractor(const MediaAnalyticsItem *);
+extern bool statsd_nuplayer(const MediaAnalyticsItem *);
+extern bool statsd_recorder(const MediaAnalyticsItem *);
+
+extern bool statsd_mediadrm(const MediaAnalyticsItem *);
+extern bool statsd_widevineCDM(const MediaAnalyticsItem *);
+extern bool statsd_drmmanager(const MediaAnalyticsItem *);
+
+} // namespace android
diff --git a/services/mediametrics/main_mediametrics.cpp b/services/mediametrics/main_mediametrics.cpp
new file mode 100644
index 0000000..4b2a9fa
--- /dev/null
+++ b/services/mediametrics/main_mediametrics.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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 "mediametrics"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include "MediaMetricsService.h"
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+
+
+int main(int argc __unused, char **argv __unused)
+{
+ using namespace android;
+
+ signal(SIGPIPE, SIG_IGN);
+
+ // to match the service name
+ // we're replacing "/system/bin/mediametrics" with "media.metrics"
+ // we add a ".", but discard the path components: we finish with a shorter string
+ strcpy(argv[0], MediaAnalyticsService::kServiceName);
+
+ defaultServiceManager()->addService(
+ String16(MediaAnalyticsService::kServiceName), new MediaAnalyticsService());
+
+ sp<ProcessState> processState(ProcessState::self());
+ // processState->setThreadPoolMaxThreadCount(8);
+ processState->startThreadPool();
+ IPCThreadState::self()->joinThreadPool();
+
+ return EXIT_SUCCESS;
+}
diff --git a/services/mediametrics/mediametrics.rc b/services/mediametrics/mediametrics.rc
new file mode 100644
index 0000000..1efde5e
--- /dev/null
+++ b/services/mediametrics/mediametrics.rc
@@ -0,0 +1,6 @@
+service mediametrics /system/bin/mediametrics
+ class main
+ user media
+ group media
+ ioprio rt 4
+ writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
diff --git a/services/mediametrics/statsd_audiopolicy.cpp b/services/mediametrics/statsd_audiopolicy.cpp
new file mode 100644
index 0000000..2f934f8
--- /dev/null
+++ b/services/mediametrics/statsd_audiopolicy.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "statsd_audiopolicy"
+#include <utils/Log.h>
+
+#include <dirent.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <statslog.h>
+
+#include "MediaMetricsService.h"
+#include "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.pb.h"
+#include "iface_statsd.h"
+
+namespace android {
+
+bool statsd_audiopolicy(const MediaAnalyticsItem *item)
+{
+ if (item == NULL) return false;
+
+ // these go into the statsd wrapper
+ const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ std::string pkgName = item->getPkgName();
+ int64_t pkgVersionCode = item->getPkgVersionCode();
+ int64_t mediaApexVersion = 0;
+
+
+ // the rest into our own proto
+ //
+ ::android::stats::mediametrics::AudioPolicyData metrics_proto;
+
+ // flesh out the protobuf we'll hand off with our data
+ //
+ //int32 char kAudioPolicyStatus[] = "android.media.audiopolicy.status";
+ int32_t status = -1;
+ if (item->getInt32("android.media.audiopolicy.status", &status)) {
+ metrics_proto.set_status(status);
+ }
+ //string char kAudioPolicyRqstSrc[] = "android.media.audiopolicy.rqst.src";
+ std::string rqst_src;
+ if (item->getString("android.media.audiopolicy.rqst.src", &rqst_src)) {
+ metrics_proto.set_request_source(std::move(rqst_src));
+ }
+ //string char kAudioPolicyRqstPkg[] = "android.media.audiopolicy.rqst.pkg";
+ std::string rqst_pkg;
+ if (item->getString("android.media.audiopolicy.rqst.pkg", &rqst_pkg)) {
+ metrics_proto.set_request_package(std::move(rqst_pkg));
+ }
+ //int32 char kAudioPolicyRqstSession[] = "android.media.audiopolicy.rqst.session";
+ int32_t rqst_session = -1;
+ if (item->getInt32("android.media.audiopolicy.rqst.session", &rqst_session)) {
+ metrics_proto.set_request_session(rqst_session);
+ }
+ //string char kAudioPolicyRqstDevice[] = "android.media.audiopolicy.rqst.device";
+ std::string rqst_device;
+ if (item->getString("android.media.audiopolicy.rqst.device", &rqst_device)) {
+ metrics_proto.set_request_device(std::move(rqst_device));
+ }
+
+ //string char kAudioPolicyActiveSrc[] = "android.media.audiopolicy.active.src";
+ std::string active_src;
+ if (item->getString("android.media.audiopolicy.active.src", &active_src)) {
+ metrics_proto.set_active_source(std::move(active_src));
+ }
+ //string char kAudioPolicyActivePkg[] = "android.media.audiopolicy.active.pkg";
+ std::string active_pkg;
+ if (item->getString("android.media.audiopolicy.active.pkg", &active_pkg)) {
+ metrics_proto.set_active_package(std::move(active_pkg));
+ }
+ //int32 char kAudioPolicyActiveSession[] = "android.media.audiopolicy.active.session";
+ int32_t active_session = -1;
+ if (item->getInt32("android.media.audiopolicy.active.session", &active_session)) {
+ metrics_proto.set_active_session(active_session);
+ }
+ //string char kAudioPolicyActiveDevice[] = "android.media.audiopolicy.active.device";
+ std::string active_device;
+ if (item->getString("android.media.audiopolicy.active.device", &active_device)) {
+ metrics_proto.set_active_device(std::move(active_device));
+ }
+
+
+ std::string serialized;
+ if (!metrics_proto.SerializeToString(&serialized)) {
+ ALOGE("Failed to serialize audipolicy metrics");
+ return false;
+ }
+
+ if (enabled_statsd) {
+ android::util::BytesField bf_serialized( serialized.c_str(), serialized.size());
+ (void)android::util::stats_write(android::util::MEDIAMETRICS_AUDIOPOLICY_REPORTED,
+ timestamp, pkgName.c_str(), pkgVersionCode,
+ mediaApexVersion,
+ bf_serialized);
+
+ } else {
+ ALOGV("NOT sending: private data (len=%zu)", strlen(serialized.c_str()));
+ }
+
+ return true;
+}
+
+};
diff --git a/services/mediametrics/statsd_audiorecord.cpp b/services/mediametrics/statsd_audiorecord.cpp
new file mode 100644
index 0000000..4e2829d
--- /dev/null
+++ b/services/mediametrics/statsd_audiorecord.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "statsd_audiorecord"
+#include <utils/Log.h>
+
+#include <dirent.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <statslog.h>
+
+#include "MediaMetricsService.h"
+#include "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.pb.h"
+#include "iface_statsd.h"
+
+namespace android {
+
+bool statsd_audiorecord(const MediaAnalyticsItem *item)
+{
+ if (item == NULL) return false;
+
+ // these go into the statsd wrapper
+ const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ std::string pkgName = item->getPkgName();
+ int64_t pkgVersionCode = item->getPkgVersionCode();
+ int64_t mediaApexVersion = 0;
+
+
+ // the rest into our own proto
+ //
+ ::android::stats::mediametrics::AudioRecordData metrics_proto;
+
+ // flesh out the protobuf we'll hand off with our data
+ //
+ std::string encoding;
+ if (item->getString("android.media.audiorecord.encoding", &encoding)) {
+ metrics_proto.set_encoding(std::move(encoding));
+ }
+
+ std::string source;
+ if (item->getString("android.media.audiorecord.source", &source)) {
+ metrics_proto.set_source(std::move(source));
+ }
+
+ int32_t latency = -1;
+ if (item->getInt32("android.media.audiorecord.latency", &latency)) {
+ metrics_proto.set_latency(latency);
+ }
+
+ int32_t samplerate = -1;
+ if (item->getInt32("android.media.audiorecord.samplerate", &samplerate)) {
+ metrics_proto.set_samplerate(samplerate);
+ }
+
+ int32_t channels = -1;
+ if (item->getInt32("android.media.audiorecord.channels", &channels)) {
+ metrics_proto.set_channels(channels);
+ }
+
+ int64_t createdMs = -1;
+ if (item->getInt64("android.media.audiorecord.createdMs", &createdMs)) {
+ metrics_proto.set_created_millis(createdMs);
+ }
+
+ int64_t durationMs = -1;
+ if (item->getInt64("android.media.audiorecord.durationMs", &durationMs)) {
+ metrics_proto.set_duration_millis(durationMs);
+ }
+
+ int32_t count = -1;
+ if (item->getInt32("android.media.audiorecord.n", &count)) {
+ metrics_proto.set_count(count);
+ }
+
+ int32_t errcode = -1;
+ if (item->getInt32("android.media.audiorecord.errcode", &errcode)) {
+ metrics_proto.set_error_code(errcode);
+ } else if (item->getInt32("android.media.audiorecord.lastError.code", &errcode)) {
+ metrics_proto.set_error_code(errcode);
+ }
+
+ std::string errfunc;
+ if (item->getString("android.media.audiorecord.errfunc", &errfunc)) {
+ metrics_proto.set_error_function(std::move(errfunc));
+ } else if (item->getString("android.media.audiorecord.lastError.at", &errfunc)) {
+ metrics_proto.set_error_function(std::move(errfunc));
+ }
+
+ // portId (int32)
+ int32_t port_id = -1;
+ if (item->getInt32("android.media.audiorecord.portId", &port_id)) {
+ metrics_proto.set_port_id(count);
+ }
+ // frameCount (int32)
+ int32_t frameCount = -1;
+ if (item->getInt32("android.media.audiorecord.frameCount", &frameCount)) {
+ metrics_proto.set_frame_count(frameCount);
+ }
+ // attributes (string)
+ std::string attributes;
+ if (item->getString("android.media.audiorecord.attributes", &attributes)) {
+ metrics_proto.set_attributes(std::move(attributes));
+ }
+ // channelMask (int64)
+ int64_t channelMask = -1;
+ if (item->getInt64("android.media.audiorecord.channelMask", &channelMask)) {
+ metrics_proto.set_channel_mask(channelMask);
+ }
+ // startcount (int64)
+ int64_t startcount = -1;
+ if (item->getInt64("android.media.audiorecord.startcount", &startcount)) {
+ metrics_proto.set_start_count(startcount);
+ }
+
+
+ std::string serialized;
+ if (!metrics_proto.SerializeToString(&serialized)) {
+ ALOGE("Failed to serialize audiorecord metrics");
+ return false;
+ }
+
+ if (enabled_statsd) {
+ android::util::BytesField bf_serialized( serialized.c_str(), serialized.size());
+ (void)android::util::stats_write(android::util::MEDIAMETRICS_AUDIORECORD_REPORTED,
+ timestamp, pkgName.c_str(), pkgVersionCode,
+ mediaApexVersion,
+ bf_serialized);
+
+ } else {
+ ALOGV("NOT sending: private data (len=%zu)", strlen(serialized.c_str()));
+ }
+
+ return true;
+}
+
+};
diff --git a/services/mediametrics/statsd_audiothread.cpp b/services/mediametrics/statsd_audiothread.cpp
new file mode 100644
index 0000000..a5bfcba
--- /dev/null
+++ b/services/mediametrics/statsd_audiothread.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "statsd_audiothread"
+#include <utils/Log.h>
+
+#include <dirent.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <statslog.h>
+
+#include "MediaMetricsService.h"
+#include "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.pb.h"
+#include "iface_statsd.h"
+
+namespace android {
+
+bool statsd_audiothread(const MediaAnalyticsItem *item)
+{
+ if (item == NULL) return false;
+
+ // these go into the statsd wrapper
+ const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ std::string pkgName = item->getPkgName();
+ int64_t pkgVersionCode = item->getPkgVersionCode();
+ int64_t mediaApexVersion = 0;
+
+
+ // the rest into our own proto
+ //
+ ::android::stats::mediametrics::AudioThreadData metrics_proto;
+
+#define MM_PREFIX "android.media.audiothread."
+
+ // flesh out the protobuf we'll hand off with our data
+ //
+ std::string mytype;
+ if (item->getString(MM_PREFIX "type", &mytype)) {
+ metrics_proto.set_type(std::move(mytype));
+ }
+ int32_t framecount = -1;
+ if (item->getInt32(MM_PREFIX "framecount", &framecount)) {
+ metrics_proto.set_framecount(framecount);
+ }
+ int32_t samplerate = -1;
+ if (item->getInt32(MM_PREFIX "samplerate", &samplerate)) {
+ metrics_proto.set_samplerate(samplerate);
+ }
+ std::string workhist;
+ if (item->getString(MM_PREFIX "workMs.hist", &workhist)) {
+ metrics_proto.set_work_millis_hist(std::move(workhist));
+ }
+ std::string latencyhist;
+ if (item->getString(MM_PREFIX "latencyMs.hist", &latencyhist)) {
+ metrics_proto.set_latency_millis_hist(std::move(latencyhist));
+ }
+ std::string warmuphist;
+ if (item->getString(MM_PREFIX "warmupMs.hist", &warmuphist)) {
+ metrics_proto.set_warmup_millis_hist(std::move(warmuphist));
+ }
+ int64_t underruns = -1;
+ if (item->getInt64(MM_PREFIX "underruns", &underruns)) {
+ metrics_proto.set_underruns(underruns);
+ }
+ int64_t overruns = -1;
+ if (item->getInt64(MM_PREFIX "overruns", &overruns)) {
+ metrics_proto.set_overruns(overruns);
+ }
+ int64_t activeMs = -1;
+ if (item->getInt64(MM_PREFIX "activeMs", &activeMs)) {
+ metrics_proto.set_active_millis(activeMs);
+ }
+ int64_t durationMs = -1;
+ if (item->getInt64(MM_PREFIX "durationMs", &durationMs)) {
+ metrics_proto.set_duration_millis(durationMs);
+ }
+
+ // item->setInt32(MM_PREFIX "id", (int32_t)mId); // IO handle
+ int32_t id = -1;
+ if (item->getInt32(MM_PREFIX "id", &id)) {
+ metrics_proto.set_id(id);
+ }
+ // item->setInt32(MM_PREFIX "portId", (int32_t)mPortId);
+ int32_t port_id = -1;
+ if (item->getInt32(MM_PREFIX "portId", &id)) {
+ metrics_proto.set_port_id(port_id);
+ }
+ // item->setCString(MM_PREFIX "type", threadTypeToString(mType));
+ std::string type;
+ if (item->getString(MM_PREFIX "type", &type)) {
+ metrics_proto.set_type(std::move(type));
+ }
+ // item->setInt32(MM_PREFIX "sampleRate", (int32_t)mSampleRate);
+ int32_t sample_rate = -1;
+ if (item->getInt32(MM_PREFIX "sampleRate", &sample_rate)) {
+ metrics_proto.set_sample_rate(sample_rate);
+ }
+ // item->setInt64(MM_PREFIX "channelMask", (int64_t)mChannelMask);
+ int32_t channel_mask = -1;
+ if (item->getInt32(MM_PREFIX "channelMask", &channel_mask)) {
+ metrics_proto.set_channel_mask(channel_mask);
+ }
+ // item->setCString(MM_PREFIX "encoding", toString(mFormat).c_str());
+ std::string encoding;
+ if (item->getString(MM_PREFIX "encoding", &encoding)) {
+ metrics_proto.set_encoding(std::move(encoding));
+ }
+ // item->setInt32(MM_PREFIX "frameCount", (int32_t)mFrameCount);
+ int32_t frame_count = -1;
+ if (item->getInt32(MM_PREFIX "frameCount", &frame_count)) {
+ metrics_proto.set_frame_count(frame_count);
+ }
+ // item->setCString(MM_PREFIX "outDevice", toString(mOutDevice).c_str());
+ std::string outDevice;
+ if (item->getString(MM_PREFIX "outDevice", &outDevice)) {
+ metrics_proto.set_output_device(std::move(outDevice));
+ }
+ // item->setCString(MM_PREFIX "inDevice", toString(mInDevice).c_str());
+ std::string inDevice;
+ if (item->getString(MM_PREFIX "inDevice", &inDevice)) {
+ metrics_proto.set_input_device(std::move(inDevice));
+ }
+ // item->setDouble(MM_PREFIX "ioJitterMs.mean", mIoJitterMs.getMean());
+ double iojitters_ms_mean = -1;
+ if (item->getDouble(MM_PREFIX "ioJitterMs.mean", &iojitters_ms_mean)) {
+ metrics_proto.set_io_jitter_mean_millis(iojitters_ms_mean);
+ }
+ // item->setDouble(MM_PREFIX "ioJitterMs.std", mIoJitterMs.getStdDev());
+ double iojitters_ms_std = -1;
+ if (item->getDouble(MM_PREFIX "ioJitterMs.std", &iojitters_ms_std)) {
+ metrics_proto.set_io_jitter_stddev_millis(iojitters_ms_std);
+ }
+ // item->setDouble(MM_PREFIX "processTimeMs.mean", mProcessTimeMs.getMean());
+ double process_time_ms_mean = -1;
+ if (item->getDouble(MM_PREFIX "processTimeMs.mean", &process_time_ms_mean)) {
+ metrics_proto.set_process_time_mean_millis(process_time_ms_mean);
+ }
+ // item->setDouble(MM_PREFIX "processTimeMs.std", mProcessTimeMs.getStdDev());
+ double process_time_ms_std = -1;
+ if (item->getDouble(MM_PREFIX "processTimeMs.std", &process_time_ms_std)) {
+ metrics_proto.set_process_time_stddev_millis(process_time_ms_std);
+ }
+ // item->setDouble(MM_PREFIX "timestampJitterMs.mean", tsjitter.getMean());
+ double timestamp_jitter_ms_mean = -1;
+ if (item->getDouble(MM_PREFIX "timestampJitterMs.mean", ×tamp_jitter_ms_mean)) {
+ metrics_proto.set_timestamp_jitter_mean_millis(timestamp_jitter_ms_mean);
+ }
+ // item->setDouble(MM_PREFIX "timestampJitterMs.std", tsjitter.getStdDev());
+ double timestamp_jitter_ms_stddev = -1;
+ if (item->getDouble(MM_PREFIX "timestampJitterMs.std", ×tamp_jitter_ms_stddev)) {
+ metrics_proto.set_timestamp_jitter_stddev_millis(timestamp_jitter_ms_stddev);
+ }
+ // item->setDouble(MM_PREFIX "latencyMs.mean", mLatencyMs.getMean());
+ double latency_ms_mean = -1;
+ if (item->getDouble(MM_PREFIX "latencyMs.mean", &latency_ms_mean)) {
+ metrics_proto.set_latency_mean_millis(latency_ms_mean);
+ }
+ // item->setDouble(MM_PREFIX "latencyMs.std", mLatencyMs.getStdDev());
+ double latency_ms_stddev = -1;
+ if (item->getDouble(MM_PREFIX "latencyMs.std", &latency_ms_stddev)) {
+ metrics_proto.set_latency_stddev_millis(latency_ms_stddev);
+ }
+
+
+ std::string serialized;
+ if (!metrics_proto.SerializeToString(&serialized)) {
+ ALOGE("Failed to serialize audiothread metrics");
+ return false;
+ }
+
+ if (enabled_statsd) {
+ android::util::BytesField bf_serialized( serialized.c_str(), serialized.size());
+ (void)android::util::stats_write(android::util::MEDIAMETRICS_AUDIOTHREAD_REPORTED,
+ timestamp, pkgName.c_str(), pkgVersionCode,
+ mediaApexVersion,
+ bf_serialized);
+
+ } else {
+ ALOGV("NOT sending: private data (len=%zu)", strlen(serialized.c_str()));
+ }
+
+ return true;
+}
+
+};
diff --git a/services/mediametrics/statsd_audiotrack.cpp b/services/mediametrics/statsd_audiotrack.cpp
new file mode 100644
index 0000000..9d76d43
--- /dev/null
+++ b/services/mediametrics/statsd_audiotrack.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "statsd_audiotrack"
+#include <utils/Log.h>
+
+#include <dirent.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <statslog.h>
+
+#include "MediaMetricsService.h"
+#include "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.pb.h"
+#include "iface_statsd.h"
+
+namespace android {
+
+bool statsd_audiotrack(const MediaAnalyticsItem *item)
+{
+ if (item == NULL) return false;
+
+ // these go into the statsd wrapper
+ const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ std::string pkgName = item->getPkgName();
+ int64_t pkgVersionCode = item->getPkgVersionCode();
+ int64_t mediaApexVersion = 0;
+
+
+ // the rest into our own proto
+ //
+ ::android::stats::mediametrics::AudioTrackData metrics_proto;
+
+ // flesh out the protobuf we'll hand off with our data
+ //
+
+ // static constexpr char kAudioTrackStreamType[] = "android.media.audiotrack.streamtype";
+ // optional string streamType;
+ std::string streamtype;
+ if (item->getString("android.media.audiotrack.streamtype", &streamtype)) {
+ metrics_proto.set_stream_type(std::move(streamtype));
+ }
+
+ // static constexpr char kAudioTrackContentType[] = "android.media.audiotrack.type";
+ // optional string contentType;
+ std::string contenttype;
+ if (item->getString("android.media.audiotrack.type", &contenttype)) {
+ metrics_proto.set_content_type(std::move(contenttype));
+ }
+
+ // static constexpr char kAudioTrackUsage[] = "android.media.audiotrack.usage";
+ // optional string trackUsage;
+ std::string trackusage;
+ if (item->getString("android.media.audiotrack.usage", &trackusage)) {
+ metrics_proto.set_track_usage(std::move(trackusage));
+ }
+
+ // static constexpr char kAudioTrackSampleRate[] = "android.media.audiotrack.samplerate";
+ // optional int32 samplerate;
+ int32_t samplerate = -1;
+ if (item->getInt32("android.media.audiotrack.samplerate", &samplerate)) {
+ metrics_proto.set_sample_rate(samplerate);
+ }
+
+ // static constexpr char kAudioTrackChannelMask[] = "android.media.audiotrack.channelmask";
+ // optional int64 channelMask;
+ int64_t channelMask = -1;
+ if (item->getInt64("android.media.audiotrack.channelmask", &channelMask)) {
+ metrics_proto.set_channel_mask(channelMask);
+ }
+
+ // NB: These are not yet exposed as public Java API constants.
+ // static constexpr char kAudioTrackUnderrunFrames[] = "android.media.audiotrack.underrunframes";
+ // optional int32 underrunframes;
+ int32_t underrunframes = -1;
+ if (item->getInt32("android.media.audiotrack.underrunframes", &underrunframes)) {
+ metrics_proto.set_underrun_frames(underrunframes);
+ }
+
+ // static constexpr char kAudioTrackStartupGlitch[] = "android.media.audiotrack.glitch.startup";
+ // optional int32 startupglitch;
+ int32_t startupglitch = -1;
+ if (item->getInt32("android.media.audiotrack.glitch.startup", &startupglitch)) {
+ metrics_proto.set_startup_glitch(startupglitch);
+ }
+
+ // portId (int32)
+ int32_t port_id = -1;
+ if (item->getInt32("android.media.audiotrack.portId", &port_id)) {
+ metrics_proto.set_port_id(port_id);
+ }
+ // encoding (string)
+ std::string encoding;
+ if (item->getString("android.media.audiotrack.encoding", &encoding)) {
+ metrics_proto.set_encoding(std::move(encoding));
+ }
+ // frameCount (int32)
+ int32_t frame_count = -1;
+ if (item->getInt32("android.media.audiotrack.frameCount", &frame_count)) {
+ metrics_proto.set_frame_count(frame_count);
+ }
+ // attributes (string)
+ std::string attributes;
+ if (item->getString("android.media.audiotrack.attributes", &attributes)) {
+ metrics_proto.set_attributes(std::move(attributes));
+ }
+
+ std::string serialized;
+ if (!metrics_proto.SerializeToString(&serialized)) {
+ ALOGE("Failed to serialize audiotrack metrics");
+ return false;
+ }
+
+ if (enabled_statsd) {
+ android::util::BytesField bf_serialized( serialized.c_str(), serialized.size());
+ (void)android::util::stats_write(android::util::MEDIAMETRICS_AUDIOTRACK_REPORTED,
+ timestamp, pkgName.c_str(), pkgVersionCode,
+ mediaApexVersion,
+ bf_serialized);
+
+ } else {
+ ALOGV("NOT sending: private data (len=%zu)", strlen(serialized.c_str()));
+ }
+
+ return true;
+}
+
+};
diff --git a/services/mediametrics/statsd_codec.cpp b/services/mediametrics/statsd_codec.cpp
new file mode 100644
index 0000000..fde0bfa
--- /dev/null
+++ b/services/mediametrics/statsd_codec.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "statsd_codec"
+#include <utils/Log.h>
+
+#include <dirent.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <statslog.h>
+
+#include "MediaMetricsService.h"
+#include "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.pb.h"
+#include "iface_statsd.h"
+
+namespace android {
+
+bool statsd_codec(const MediaAnalyticsItem *item)
+{
+ if (item == NULL) return false;
+
+ // these go into the statsd wrapper
+ const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ std::string pkgName = item->getPkgName();
+ int64_t pkgVersionCode = item->getPkgVersionCode();
+ int64_t mediaApexVersion = 0;
+
+
+ // the rest into our own proto
+ //
+ ::android::stats::mediametrics::CodecData metrics_proto;
+
+ // flesh out the protobuf we'll hand off with our data
+ //
+ // android.media.mediacodec.codec string
+ std::string codec;
+ if (item->getString("android.media.mediacodec.codec", &codec)) {
+ metrics_proto.set_codec(std::move(codec));
+ }
+ // android.media.mediacodec.mime string
+ std::string mime;
+ if (item->getString("android.media.mediacodec.mime", &mime)) {
+ metrics_proto.set_mime(std::move(mime));
+ }
+ // android.media.mediacodec.mode string
+ std::string mode;
+ if ( item->getString("android.media.mediacodec.mode", &mode)) {
+ metrics_proto.set_mode(std::move(mode));
+ }
+ // android.media.mediacodec.encoder int32
+ int32_t encoder = -1;
+ if ( item->getInt32("android.media.mediacodec.encoder", &encoder)) {
+ metrics_proto.set_encoder(encoder);
+ }
+ // android.media.mediacodec.secure int32
+ int32_t secure = -1;
+ if ( item->getInt32("android.media.mediacodec.secure", &secure)) {
+ metrics_proto.set_secure(secure);
+ }
+ // android.media.mediacodec.width int32
+ int32_t width = -1;
+ if ( item->getInt32("android.media.mediacodec.width", &width)) {
+ metrics_proto.set_width(width);
+ }
+ // android.media.mediacodec.height int32
+ int32_t height = -1;
+ if ( item->getInt32("android.media.mediacodec.height", &height)) {
+ metrics_proto.set_height(height);
+ }
+ // android.media.mediacodec.rotation-degrees int32
+ int32_t rotation = -1;
+ if ( item->getInt32("android.media.mediacodec.rotation-degrees", &rotation)) {
+ metrics_proto.set_rotation(rotation);
+ }
+ // android.media.mediacodec.crypto int32 (although missing if not needed
+ int32_t crypto = -1;
+ if ( item->getInt32("android.media.mediacodec.crypto", &crypto)) {
+ metrics_proto.set_crypto(crypto);
+ }
+ // android.media.mediacodec.profile int32
+ int32_t profile = -1;
+ if ( item->getInt32("android.media.mediacodec.profile", &profile)) {
+ metrics_proto.set_profile(profile);
+ }
+ // android.media.mediacodec.level int32
+ int32_t level = -1;
+ if ( item->getInt32("android.media.mediacodec.level", &level)) {
+ metrics_proto.set_level(level);
+ }
+ // android.media.mediacodec.maxwidth int32
+ int32_t maxwidth = -1;
+ if ( item->getInt32("android.media.mediacodec.maxwidth", &maxwidth)) {
+ metrics_proto.set_max_width(maxwidth);
+ }
+ // android.media.mediacodec.maxheight int32
+ int32_t maxheight = -1;
+ if ( item->getInt32("android.media.mediacodec.maxheight", &maxheight)) {
+ metrics_proto.set_max_height(maxheight);
+ }
+ // android.media.mediacodec.errcode int32
+ int32_t errcode = -1;
+ if ( item->getInt32("android.media.mediacodec.errcode", &errcode)) {
+ metrics_proto.set_error_code(errcode);
+ }
+ // android.media.mediacodec.errstate string
+ std::string errstate;
+ if ( item->getString("android.media.mediacodec.errstate", &errstate)) {
+ metrics_proto.set_error_state(std::move(errstate));
+ }
+ // android.media.mediacodec.latency.max int64
+ int64_t latency_max = -1;
+ if ( item->getInt64("android.media.mediacodec.latency.max", &latency_max)) {
+ metrics_proto.set_latency_max(latency_max);
+ }
+ // android.media.mediacodec.latency.min int64
+ int64_t latency_min = -1;
+ if ( item->getInt64("android.media.mediacodec.latency.min", &latency_min)) {
+ metrics_proto.set_latency_min(latency_min);
+ }
+ // android.media.mediacodec.latency.avg int64
+ int64_t latency_avg = -1;
+ if ( item->getInt64("android.media.mediacodec.latency.avg", &latency_avg)) {
+ metrics_proto.set_latency_avg(latency_avg);
+ }
+ // android.media.mediacodec.latency.n int64
+ int64_t latency_count = -1;
+ if ( item->getInt64("android.media.mediacodec.latency.n", &latency_count)) {
+ metrics_proto.set_latency_count(latency_count);
+ }
+ // android.media.mediacodec.latency.unknown int64
+ int64_t latency_unknown = -1;
+ if ( item->getInt64("android.media.mediacodec.latency.unknown", &latency_unknown)) {
+ metrics_proto.set_latency_unknown(latency_unknown);
+ }
+ // android.media.mediacodec.latency.hist NOT EMITTED
+
+ std::string serialized;
+ if (!metrics_proto.SerializeToString(&serialized)) {
+ ALOGE("Failed to serialize codec metrics");
+ return false;
+ }
+
+ if (enabled_statsd) {
+ android::util::BytesField bf_serialized( serialized.c_str(), serialized.size());
+ (void)android::util::stats_write(android::util::MEDIAMETRICS_CODEC_REPORTED,
+ timestamp, pkgName.c_str(), pkgVersionCode,
+ mediaApexVersion,
+ bf_serialized);
+
+ } else {
+ ALOGV("NOT sending: private data (len=%zu)", strlen(serialized.c_str()));
+ }
+
+ return true;
+}
+
+};
diff --git a/services/mediametrics/statsd_drm.cpp b/services/mediametrics/statsd_drm.cpp
new file mode 100644
index 0000000..78d0a22
--- /dev/null
+++ b/services/mediametrics/statsd_drm.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "statsd_drm"
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <string.h>
+#include <pwd.h>
+
+#include "MediaMetricsService.h"
+#include "iface_statsd.h"
+
+#include <statslog.h>
+
+namespace android {
+
+// mediadrm
+bool statsd_mediadrm(const MediaAnalyticsItem *item)
+{
+ if (item == NULL) return false;
+
+ const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ std::string pkgName = item->getPkgName();
+ int64_t pkgVersionCode = item->getPkgVersionCode();
+ int64_t mediaApexVersion = 0;
+
+ char *vendor = NULL;
+ (void) item->getCString("vendor", &vendor);
+ char *description = NULL;
+ (void) item->getCString("description", &description);
+ char *serialized_metrics = NULL;
+ (void) item->getCString("serialized_metrics", &serialized_metrics);
+
+ if (enabled_statsd) {
+ android::util::BytesField bf_serialized(serialized_metrics ? serialized_metrics : NULL,
+ serialized_metrics ? strlen(serialized_metrics)
+ : 0);
+ android::util::stats_write(android::util::MEDIAMETRICS_MEDIADRM_REPORTED,
+ timestamp, pkgName.c_str(), pkgVersionCode,
+ mediaApexVersion,
+ vendor, description,
+ bf_serialized);
+ } else {
+ ALOGV("NOT sending: mediadrm private data (len=%zu)",
+ serialized_metrics ? strlen(serialized_metrics) : 0);
+ }
+
+ free(vendor);
+ free(description);
+ free(serialized_metrics);
+ return true;
+}
+
+// widevineCDM
+bool statsd_widevineCDM(const MediaAnalyticsItem *item)
+{
+ if (item == NULL) return false;
+
+ const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ std::string pkgName = item->getPkgName();
+ int64_t pkgVersionCode = item->getPkgVersionCode();
+ int64_t mediaApexVersion = 0;
+
+ char *serialized_metrics = NULL;
+ (void) item->getCString("serialized_metrics", &serialized_metrics);
+
+ if (enabled_statsd) {
+ android::util::BytesField bf_serialized(serialized_metrics ? serialized_metrics : NULL,
+ serialized_metrics ? strlen(serialized_metrics)
+ : 0);
+ android::util::stats_write(android::util::MEDIAMETRICS_DRM_WIDEVINE_REPORTED,
+ timestamp, pkgName.c_str(), pkgVersionCode,
+ mediaApexVersion,
+ bf_serialized);
+ } else {
+ ALOGV("NOT sending: widevine private data (len=%zu)",
+ serialized_metrics ? strlen(serialized_metrics) : 0);
+ }
+
+ free(serialized_metrics);
+ return true;
+}
+
+// drmmanager
+bool statsd_drmmanager(const MediaAnalyticsItem *item)
+{
+ if (item == NULL) return false;
+
+ const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ std::string pkgName = item->getPkgName();
+ int64_t pkgVersionCode = item->getPkgVersionCode();
+ int64_t mediaApexVersion = 0;
+
+ char *plugin_id = NULL;
+ (void) item->getCString("plugin_id", &plugin_id);
+ char *description = NULL;
+ (void) item->getCString("description", &description);
+ int32_t method_id = -1;
+ (void) item->getInt32("method_id", &method_id);
+ char *mime_types = NULL;
+ (void) item->getCString("mime_types", &mime_types);
+
+ if (enabled_statsd) {
+ android::util::stats_write(android::util::MEDIAMETRICS_DRMMANAGER_REPORTED,
+ timestamp, pkgName.c_str(), pkgVersionCode,
+ mediaApexVersion,
+ plugin_id, description,
+ method_id, mime_types);
+ } else {
+ ALOGV("NOT sending: drmmanager data");
+ }
+
+ free(plugin_id);
+ free(description);
+ free(mime_types);
+ return true;
+}
+} // namespace android
diff --git a/services/mediametrics/statsd_extractor.cpp b/services/mediametrics/statsd_extractor.cpp
new file mode 100644
index 0000000..cc62241
--- /dev/null
+++ b/services/mediametrics/statsd_extractor.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "statsd_extractor"
+#include <utils/Log.h>
+
+#include <dirent.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <statslog.h>
+
+#include "MediaMetricsService.h"
+#include "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.pb.h"
+#include "iface_statsd.h"
+
+namespace android {
+
+bool statsd_extractor(const MediaAnalyticsItem *item)
+{
+ if (item == NULL) return false;
+
+ // these go into the statsd wrapper
+ const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ std::string pkgName = item->getPkgName();
+ int64_t pkgVersionCode = item->getPkgVersionCode();
+ int64_t mediaApexVersion = 0;
+
+
+ // the rest into our own proto
+ //
+ ::android::stats::mediametrics::ExtractorData metrics_proto;
+
+ // flesh out the protobuf we'll hand off with our data
+ //
+
+ // android.media.mediaextractor.fmt string
+ std::string fmt;
+ if (item->getString("android.media.mediaextractor.fmt", &fmt)) {
+ metrics_proto.set_format(std::move(fmt));
+ }
+ // android.media.mediaextractor.mime string
+ std::string mime;
+ if (item->getString("android.media.mediaextractor.mime", &mime)) {
+ metrics_proto.set_mime(std::move(mime));
+ }
+ // android.media.mediaextractor.ntrk int32
+ int32_t ntrk = -1;
+ if (item->getInt32("android.media.mediaextractor.ntrk", &ntrk)) {
+ metrics_proto.set_tracks(ntrk);
+ }
+
+ std::string serialized;
+ if (!metrics_proto.SerializeToString(&serialized)) {
+ ALOGE("Failed to serialize extractor metrics");
+ return false;
+ }
+
+ if (enabled_statsd) {
+ android::util::BytesField bf_serialized( serialized.c_str(), serialized.size());
+ (void)android::util::stats_write(android::util::MEDIAMETRICS_EXTRACTOR_REPORTED,
+ timestamp, pkgName.c_str(), pkgVersionCode,
+ mediaApexVersion,
+ bf_serialized);
+
+ } else {
+ ALOGV("NOT sending: private data (len=%zu)", strlen(serialized.c_str()));
+ }
+
+ return true;
+}
+
+};
diff --git a/services/mediametrics/statsd_nuplayer.cpp b/services/mediametrics/statsd_nuplayer.cpp
new file mode 100644
index 0000000..9db1e81
--- /dev/null
+++ b/services/mediametrics/statsd_nuplayer.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "statsd_nuplayer"
+#include <utils/Log.h>
+
+#include <dirent.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <statslog.h>
+
+#include "MediaMetricsService.h"
+#include "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.pb.h"
+#include "iface_statsd.h"
+
+namespace android {
+
+/*
+ * handles nuplayer AND nuplayer2
+ * checks for the union of what the two players generate
+ */
+bool statsd_nuplayer(const MediaAnalyticsItem *item)
+{
+ if (item == NULL) return false;
+
+ // these go into the statsd wrapper
+ const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ std::string pkgName = item->getPkgName();
+ int64_t pkgVersionCode = item->getPkgVersionCode();
+ int64_t mediaApexVersion = 0;
+
+
+ // the rest into our own proto
+ //
+ ::android::stats::mediametrics::NuPlayerData metrics_proto;
+
+ // flesh out the protobuf we'll hand off with our data
+ //
+
+ // differentiate between nuplayer and nuplayer2
+ metrics_proto.set_whichplayer(item->getKey().c_str());
+
+ std::string video_mime;
+ if (item->getString("android.media.mediaplayer.video.mime", &video_mime)) {
+ metrics_proto.set_video_mime(std::move(video_mime));
+ }
+ std::string video_codec;
+ if (item->getString("android.media.mediaplayer.video.codec", &video_codec)) {
+ metrics_proto.set_video_codec(std::move(video_codec));
+ }
+
+ int32_t width = -1;
+ if (item->getInt32("android.media.mediaplayer.width", &width)) {
+ metrics_proto.set_width(width);
+ }
+ int32_t height = -1;
+ if (item->getInt32("android.media.mediaplayer.height", &height)) {
+ metrics_proto.set_height(height);
+ }
+
+ int64_t frames = -1;
+ if (item->getInt64("android.media.mediaplayer.frames", &frames)) {
+ metrics_proto.set_frames(frames);
+ }
+ int64_t frames_dropped = -1;
+ if (item->getInt64("android.media.mediaplayer.dropped", &frames_dropped)) {
+ metrics_proto.set_frames_dropped(frames_dropped);
+ }
+ int64_t frames_dropped_startup = -1;
+ if (item->getInt64("android.media.mediaplayer.startupdropped", &frames_dropped_startup)) {
+ metrics_proto.set_frames_dropped_startup(frames_dropped_startup);
+ }
+ double fps = -1.0;
+ if (item->getDouble("android.media.mediaplayer.fps", &fps)) {
+ metrics_proto.set_framerate(fps);
+ }
+
+ std::string audio_mime;
+ if (item->getString("android.media.mediaplayer.audio.mime", &audio_mime)) {
+ metrics_proto.set_audio_mime(std::move(audio_mime));
+ }
+ std::string audio_codec;
+ if (item->getString("android.media.mediaplayer.audio.codec", &audio_codec)) {
+ metrics_proto.set_audio_codec(std::move(audio_codec));
+ }
+
+ int64_t duration_ms = -1;
+ if (item->getInt64("android.media.mediaplayer.durationMs", &duration_ms)) {
+ metrics_proto.set_duration_millis(duration_ms);
+ }
+ int64_t playing_ms = -1;
+ if (item->getInt64("android.media.mediaplayer.playingMs", &playing_ms)) {
+ metrics_proto.set_playing_millis(playing_ms);
+ }
+
+ int32_t err = -1;
+ if (item->getInt32("android.media.mediaplayer.err", &err)) {
+ metrics_proto.set_error(err);
+ }
+ int32_t error_code = -1;
+ if (item->getInt32("android.media.mediaplayer.errcode", &error_code)) {
+ metrics_proto.set_error_code(error_code);
+ }
+ std::string error_state;
+ if (item->getString("android.media.mediaplayer.errstate", &error_state)) {
+ metrics_proto.set_error_state(std::move(error_state));
+ }
+
+ std::string data_source_type;
+ if (item->getString("android.media.mediaplayer.dataSource", &data_source_type)) {
+ metrics_proto.set_data_source_type(std::move(data_source_type));
+ }
+
+ int64_t rebufferingMs = -1;
+ if (item->getInt64("android.media.mediaplayer.rebufferingMs", &rebufferingMs)) {
+ metrics_proto.set_rebuffering_millis(rebufferingMs);
+ }
+ int32_t rebuffers = -1;
+ if (item->getInt32("android.media.mediaplayer.rebuffers", &rebuffers)) {
+ metrics_proto.set_rebuffers(rebuffers);
+ }
+ int32_t rebufferExit = -1;
+ if (item->getInt32("android.media.mediaplayer.rebufferExit", &rebufferExit)) {
+ metrics_proto.set_rebuffer_at_exit(rebufferExit);
+ }
+
+
+ std::string serialized;
+ if (!metrics_proto.SerializeToString(&serialized)) {
+ ALOGE("Failed to serialize nuplayer metrics");
+ return false;
+ }
+
+ if (enabled_statsd) {
+ android::util::BytesField bf_serialized( serialized.c_str(), serialized.size());
+ (void)android::util::stats_write(android::util::MEDIAMETRICS_NUPLAYER_REPORTED,
+ timestamp, pkgName.c_str(), pkgVersionCode,
+ mediaApexVersion,
+ bf_serialized);
+
+ } else {
+ ALOGV("NOT sending: private data (len=%zu)", strlen(serialized.c_str()));
+ }
+
+ return true;
+}
+
+};
diff --git a/services/mediametrics/statsd_recorder.cpp b/services/mediametrics/statsd_recorder.cpp
new file mode 100644
index 0000000..972a221
--- /dev/null
+++ b/services/mediametrics/statsd_recorder.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "statsd_recorder"
+#include <utils/Log.h>
+
+#include <dirent.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <statslog.h>
+
+#include "MediaMetricsService.h"
+#include "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.pb.h"
+#include "iface_statsd.h"
+
+namespace android {
+
+bool statsd_recorder(const MediaAnalyticsItem *item)
+{
+ if (item == NULL) return false;
+
+ // these go into the statsd wrapper
+ const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ std::string pkgName = item->getPkgName();
+ int64_t pkgVersionCode = item->getPkgVersionCode();
+ int64_t mediaApexVersion = 0;
+
+
+ // the rest into our own proto
+ //
+ ::android::stats::mediametrics::RecorderData metrics_proto;
+
+ // flesh out the protobuf we'll hand off with our data
+ //
+
+ // string kRecorderAudioMime = "android.media.mediarecorder.audio.mime";
+ std::string audio_mime;
+ if (item->getString("android.media.mediarecorder.audio.mime", &audio_mime)) {
+ metrics_proto.set_audio_mime(std::move(audio_mime));
+ }
+ // string kRecorderVideoMime = "android.media.mediarecorder.video.mime";
+ std::string video_mime;
+ if (item->getString("android.media.mediarecorder.video.mime", &video_mime)) {
+ metrics_proto.set_video_mime(std::move(video_mime));
+ }
+ // int32 kRecorderVideoProfile = "android.media.mediarecorder.video-encoder-profile";
+ int32_t videoProfile = -1;
+ if (item->getInt32("android.media.mediarecorder.video-encoder-profile", &videoProfile)) {
+ metrics_proto.set_video_profile(videoProfile);
+ }
+ // int32 kRecorderVideoLevel = "android.media.mediarecorder.video-encoder-level";
+ int32_t videoLevel = -1;
+ if (item->getInt32("android.media.mediarecorder.video-encoder-level", &videoLevel)) {
+ metrics_proto.set_video_level(videoLevel);
+ }
+ // int32 kRecorderWidth = "android.media.mediarecorder.width";
+ int32_t width = -1;
+ if (item->getInt32("android.media.mediarecorder.width", &width)) {
+ metrics_proto.set_width(width);
+ }
+ // int32 kRecorderHeight = "android.media.mediarecorder.height";
+ int32_t height = -1;
+ if (item->getInt32("android.media.mediarecorder.height", &height)) {
+ metrics_proto.set_height(height);
+ }
+ // int32 kRecorderRotation = "android.media.mediarecorder.rotation";
+ int32_t rotation = -1; // default to 0?
+ if (item->getInt32("android.media.mediarecorder.rotation", &rotation)) {
+ metrics_proto.set_rotation(rotation);
+ }
+ // int32 kRecorderFrameRate = "android.media.mediarecorder.frame-rate";
+ int32_t framerate = -1;
+ if (item->getInt32("android.media.mediarecorder.frame-rate", &framerate)) {
+ metrics_proto.set_framerate(framerate);
+ }
+
+ // int32 kRecorderCaptureFps = "android.media.mediarecorder.capture-fps";
+ int32_t captureFps = -1;
+ if (item->getInt32("android.media.mediarecorder.capture-fps", &captureFps)) {
+ metrics_proto.set_capture_fps(captureFps);
+ }
+ // double kRecorderCaptureFpsEnable = "android.media.mediarecorder.capture-fpsenable";
+ double captureFpsEnable = -1;
+ if (item->getDouble("android.media.mediarecorder.capture-fpsenable", &captureFpsEnable)) {
+ metrics_proto.set_capture_fps_enable(captureFpsEnable);
+ }
+
+ // int64 kRecorderDurationMs = "android.media.mediarecorder.durationMs";
+ int64_t durationMs = -1;
+ if (item->getInt64("android.media.mediarecorder.durationMs", &durationMs)) {
+ metrics_proto.set_duration_millis(durationMs);
+ }
+ // int64 kRecorderPaused = "android.media.mediarecorder.pausedMs";
+ int64_t pausedMs = -1;
+ if (item->getInt64("android.media.mediarecorder.pausedMs", &pausedMs)) {
+ metrics_proto.set_paused_millis(pausedMs);
+ }
+ // int32 kRecorderNumPauses = "android.media.mediarecorder.NPauses";
+ int32_t pausedCount = -1;
+ if (item->getInt32("android.media.mediarecorder.NPauses", &pausedCount)) {
+ metrics_proto.set_paused_count(pausedCount);
+ }
+
+ // int32 kRecorderAudioBitrate = "android.media.mediarecorder.audio-bitrate";
+ int32_t audioBitrate = -1;
+ if (item->getInt32("android.media.mediarecorder.audio-bitrate", &audioBitrate)) {
+ metrics_proto.set_audio_bitrate(audioBitrate);
+ }
+ // int32 kRecorderAudioChannels = "android.media.mediarecorder.audio-channels";
+ int32_t audioChannels = -1;
+ if (item->getInt32("android.media.mediarecorder.audio-channels", &audioChannels)) {
+ metrics_proto.set_audio_channels(audioChannels);
+ }
+ // int32 kRecorderAudioSampleRate = "android.media.mediarecorder.audio-samplerate";
+ int32_t audioSampleRate = -1;
+ if (item->getInt32("android.media.mediarecorder.audio-samplerate", &audioSampleRate)) {
+ metrics_proto.set_audio_samplerate(audioSampleRate);
+ }
+
+ // int32 kRecorderMovieTimescale = "android.media.mediarecorder.movie-timescale";
+ int32_t movieTimescale = -1;
+ if (item->getInt32("android.media.mediarecorder.movie-timescale", &movieTimescale)) {
+ metrics_proto.set_movie_timescale(movieTimescale);
+ }
+ // int32 kRecorderAudioTimescale = "android.media.mediarecorder.audio-timescale";
+ int32_t audioTimescale = -1;
+ if (item->getInt32("android.media.mediarecorder.audio-timescale", &audioTimescale)) {
+ metrics_proto.set_audio_timescale(audioTimescale);
+ }
+ // int32 kRecorderVideoTimescale = "android.media.mediarecorder.video-timescale";
+ int32_t videoTimescale = -1;
+ if (item->getInt32("android.media.mediarecorder.video-timescale", &videoTimescale)) {
+ metrics_proto.set_video_timescale(videoTimescale);
+ }
+
+ // int32 kRecorderVideoBitrate = "android.media.mediarecorder.video-bitrate";
+ int32_t videoBitRate = -1;
+ if (item->getInt32("android.media.mediarecorder.video-bitrate", &videoBitRate)) {
+ metrics_proto.set_video_bitrate(videoBitRate);
+ }
+ // int32 kRecorderVideoIframeInterval = "android.media.mediarecorder.video-iframe-interval";
+ int32_t iFrameInterval = -1;
+ if (item->getInt32("android.media.mediarecorder.video-iframe-interval", &iFrameInterval)) {
+ metrics_proto.set_iframe_interval(iFrameInterval);
+ }
+
+ std::string serialized;
+ if (!metrics_proto.SerializeToString(&serialized)) {
+ ALOGE("Failed to serialize recorder metrics");
+ return false;
+ }
+
+ if (enabled_statsd) {
+ android::util::BytesField bf_serialized( serialized.c_str(), serialized.size());
+ (void)android::util::stats_write(android::util::MEDIAMETRICS_RECORDER_REPORTED,
+ timestamp, pkgName.c_str(), pkgVersionCode,
+ mediaApexVersion,
+ bf_serialized);
+
+ } else {
+ ALOGV("NOT sending: private data (len=%zu)", strlen(serialized.c_str()));
+ }
+
+ return true;
+}
+
+};
diff --git a/services/mediametrics/tests/Android.bp b/services/mediametrics/tests/Android.bp
new file mode 100644
index 0000000..9eb2d89
--- /dev/null
+++ b/services/mediametrics/tests/Android.bp
@@ -0,0 +1,26 @@
+cc_test {
+ name: "mediametrics_tests",
+ test_suites: ["device-tests"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+
+ include_dirs: [
+ "frameworks/av/services/mediametrics",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "liblog",
+ "libmediametrics",
+ "libmediametricsservice",
+ "libutils",
+ ],
+
+ srcs: [
+ "mediametrics_tests.cpp",
+ ],
+}
diff --git a/services/mediametrics/tests/build_and_run_all_unit_tests.sh b/services/mediametrics/tests/build_and_run_all_unit_tests.sh
new file mode 100755
index 0000000..2511c30
--- /dev/null
+++ b/services/mediametrics/tests/build_and_run_all_unit_tests.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Run tests in this directory.
+#
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Android build environment not set"
+ exit -1
+fi
+
+# ensure we have mm
+. $ANDROID_BUILD_TOP/build/envsetup.sh
+
+mm
+
+echo "waiting for device"
+
+adb root && adb wait-for-device remount
+
+echo "========================================"
+
+echo "testing mediametrics"
+adb push $OUT/data/nativetest/mediametrics_tests/mediametrics_tests /system/bin
+adb shell /system/bin/mediametrics_tests
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
new file mode 100644
index 0000000..808da7b
--- /dev/null
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -0,0 +1,611 @@
+/*
+ * Copyright (C) 2019 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 "mediametrics_tests"
+#include <utils/Log.h>
+
+#include "MediaMetricsService.h"
+
+#include <stdio.h>
+
+#include <gtest/gtest.h>
+#include <media/MediaAnalyticsItem.h>
+
+using namespace android;
+
+static size_t countNewlines(const char *s) {
+ size_t count = 0;
+ while ((s = strchr(s, '\n')) != nullptr) {
+ ++s;
+ ++count;
+ }
+ return count;
+}
+
+TEST(mediametrics_tests, instantiate) {
+ sp mediaMetrics = new MediaAnalyticsService();
+ status_t status;
+
+ // random keys ignored when empty
+ std::unique_ptr<MediaAnalyticsItem> random_key(MediaAnalyticsItem::create("random_key"));
+ status = mediaMetrics->submit(random_key.get());
+ ASSERT_EQ(PERMISSION_DENIED, status);
+
+ // random keys ignored with data
+ random_key->setInt32("foo", 10);
+ status = mediaMetrics->submit(random_key.get());
+ ASSERT_EQ(PERMISSION_DENIED, status);
+
+ // known keys ignored if empty
+ std::unique_ptr<MediaAnalyticsItem> audiotrack_key(MediaAnalyticsItem::create("audiotrack"));
+ status = mediaMetrics->submit(audiotrack_key.get());
+ ASSERT_EQ(BAD_VALUE, status);
+
+ // known keys not ignored if not empty
+ audiotrack_key->addInt32("foo", 10);
+ status = mediaMetrics->submit(audiotrack_key.get());
+ ASSERT_EQ(NO_ERROR, status);
+
+
+ /*
+ // fluent style that goes directly to mediametrics
+ ASSERT_EQ(true, MediaAnalyticsItem("audiorecord")
+ .setInt32("value", 2)
+ .addInt32("bar", 1)
+ .addInt32("value", 3)
+ .selfrecord());
+ */
+
+ mediaMetrics->dump(fileno(stdout), {} /* args */);
+}
+
+TEST(mediametrics_tests, item_manipulation) {
+ MediaAnalyticsItem item("audiorecord");
+
+ item.setInt32("value", 2).addInt32("bar", 3).addInt32("value", 4);
+
+ int32_t i32;
+ ASSERT_TRUE(item.getInt32("value", &i32));
+ ASSERT_EQ(6, i32);
+
+ ASSERT_TRUE(item.getInt32("bar", &i32));
+ ASSERT_EQ(3, i32);
+
+ item.setInt64("big", INT64_MAX).setInt64("smaller", INT64_MAX - 1).addInt64("smaller", -2);
+
+ int64_t i64;
+ ASSERT_TRUE(item.getInt64("big", &i64));
+ ASSERT_EQ(INT64_MAX, i64);
+
+ ASSERT_TRUE(item.getInt64("smaller", &i64));
+ ASSERT_EQ(INT64_MAX - 3, i64);
+
+ item.setDouble("precise", 10.5).setDouble("small", 0.125).addDouble("precise", 0.25);
+
+ double d;
+ ASSERT_TRUE(item.getDouble("precise", &d));
+ ASSERT_EQ(10.75, d);
+
+ ASSERT_TRUE(item.getDouble("small", &d));
+ ASSERT_EQ(0.125, d);
+
+ char *s;
+ item.setCString("name", "Frank").setCString("mother", "June").setCString("mother", "July");
+ ASSERT_TRUE(item.getCString("name", &s));
+ ASSERT_EQ(0, strcmp(s, "Frank"));
+ free(s);
+
+ ASSERT_TRUE(item.getCString("mother", &s));
+ ASSERT_EQ(0, strcmp(s, "July")); // "July" overwrites "June"
+ free(s);
+
+ item.setRate("burgersPerHour", 5, 2);
+ int64_t b, h;
+ ASSERT_TRUE(item.getRate("burgersPerHour", &b, &h, &d));
+ ASSERT_EQ(5, b);
+ ASSERT_EQ(2, h);
+ ASSERT_EQ(2.5, d);
+
+ item.addRate("burgersPerHour", 4, 2);
+ ASSERT_TRUE(item.getRate("burgersPerHour", &b, &h, &d));
+ ASSERT_EQ(9, b);
+ ASSERT_EQ(4, h);
+ ASSERT_EQ(2.25, d);
+
+ printf("item: %s\n", item.toString().c_str());
+ fflush(stdout);
+
+ sp mediaMetrics = new MediaAnalyticsService();
+ status_t status = mediaMetrics->submit(&item);
+ ASSERT_EQ(NO_ERROR, status);
+ mediaMetrics->dump(fileno(stdout), {} /* args */);
+}
+
+TEST(mediametrics_tests, superbig_item) {
+ MediaAnalyticsItem item("TheBigOne");
+ constexpr size_t count = 10000;
+
+ for (size_t i = 0; i < count; ++i) {
+ item.setInt32(std::to_string(i).c_str(), i);
+ }
+ for (size_t i = 0; i < count; ++i) {
+ int32_t i32;
+ ASSERT_TRUE(item.getInt32(std::to_string(i).c_str(), &i32));
+ ASSERT_EQ((int32_t)i, i32);
+ }
+}
+
+TEST(mediametrics_tests, superbig_item_removal) {
+ MediaAnalyticsItem item("TheOddBigOne");
+ constexpr size_t count = 10000;
+
+ for (size_t i = 0; i < count; ++i) {
+ item.setInt32(std::to_string(i).c_str(), i);
+ }
+ for (size_t i = 0; i < count; i += 2) {
+ item.filter(std::to_string(i).c_str()); // filter out all the evens.
+ }
+ for (size_t i = 0; i < count; ++i) {
+ int32_t i32;
+ if (i & 1) { // check to see that only the odds are left.
+ ASSERT_TRUE(item.getInt32(std::to_string(i).c_str(), &i32));
+ ASSERT_EQ((int32_t)i, i32);
+ } else {
+ ASSERT_FALSE(item.getInt32(std::to_string(i).c_str(), &i32));
+ }
+ }
+}
+
+TEST(mediametrics_tests, superbig_item_removal2) {
+ MediaAnalyticsItem item("TheOne");
+ constexpr size_t count = 10000;
+
+ for (size_t i = 0; i < count; ++i) {
+ item.setInt32(std::to_string(i).c_str(), i);
+ }
+ static const char *attrs[] = { "1", };
+ item.filterNot(1, attrs);
+
+ for (size_t i = 0; i < count; ++i) {
+ int32_t i32;
+ if (i == 1) { // check to see that there is only one
+ ASSERT_TRUE(item.getInt32(std::to_string(i).c_str(), &i32));
+ ASSERT_EQ((int32_t)i, i32);
+ } else {
+ ASSERT_FALSE(item.getInt32(std::to_string(i).c_str(), &i32));
+ }
+ }
+}
+
+TEST(mediametrics_tests, item_transmutation) {
+ MediaAnalyticsItem item("Alchemist's Stone");
+
+ item.setInt64("convert", 123);
+ int64_t i64;
+ ASSERT_TRUE(item.getInt64("convert", &i64));
+ ASSERT_EQ(123, i64);
+
+ item.addInt32("convert", 2); // changes type of 'convert' from i64 to i32 (and re-init).
+ ASSERT_FALSE(item.getInt64("convert", &i64)); // should be false, no value in i64.
+
+ int32_t i32;
+ ASSERT_TRUE(item.getInt32("convert", &i32)); // check it is i32 and 2 (123 is discarded).
+ ASSERT_EQ(2, i32);
+}
+
+TEST(mediametrics_tests, item_binderization) {
+ MediaAnalyticsItem item;
+ item.setInt32("i32", 1)
+ .setInt64("i64", 2)
+ .setDouble("double", 3.1)
+ .setCString("string", "abc")
+ .setRate("rate", 11, 12);
+
+ Parcel p;
+ item.writeToParcel(&p);
+
+ p.setDataPosition(0); // rewind for reading
+ MediaAnalyticsItem item2;
+ item2.readFromParcel(p);
+
+ ASSERT_EQ(item, item2);
+}
+
+TEST(mediametrics_tests, item_byteserialization) {
+ MediaAnalyticsItem item;
+ item.setInt32("i32", 1)
+ .setInt64("i64", 2)
+ .setDouble("double", 3.1)
+ .setCString("string", "abc")
+ .setRate("rate", 11, 12);
+
+ char *data;
+ size_t length;
+ ASSERT_EQ(0, item.writeToByteString(&data, &length));
+ ASSERT_GT(length, (size_t)0);
+
+ MediaAnalyticsItem item2;
+ item2.readFromByteString(data, length);
+
+ printf("item: %s\n", item.toString().c_str());
+ printf("item2: %s\n", item2.toString().c_str());
+ ASSERT_EQ(item, item2);
+
+ free(data);
+}
+
+TEST(mediametrics_tests, item_iteration) {
+ MediaAnalyticsItem item;
+ item.setInt32("i32", 1)
+ .setInt64("i64", 2)
+ .setDouble("double", 3.125)
+ .setCString("string", "abc")
+ .setRate("rate", 11, 12);
+
+ int mask = 0;
+ for (auto &prop : item) {
+ const char *name = prop.getName();
+ if (!strcmp(name, "i32")) {
+ int32_t i32;
+ ASSERT_TRUE(prop.get(&i32));
+ ASSERT_EQ(1, i32);
+ mask |= 1;
+ } else if (!strcmp(name, "i64")) {
+ int64_t i64;
+ ASSERT_TRUE(prop.get(&i64));
+ ASSERT_EQ(2, i64);
+ mask |= 2;
+ } else if (!strcmp(name, "double")) {
+ double d;
+ ASSERT_TRUE(prop.get(&d));
+ ASSERT_EQ(3.125, d);
+ mask |= 4;
+ } else if (!strcmp(name, "string")) {
+ const char *s;
+ ASSERT_TRUE(prop.get(&s));
+ ASSERT_EQ(0, strcmp(s, "abc"));
+ mask |= 8;
+ } else if (!strcmp(name, "rate")) {
+ std::pair<int64_t, int64_t> r;
+ ASSERT_TRUE(prop.get(&r));
+ ASSERT_EQ(11, r.first);
+ ASSERT_EQ(12, r.second);
+ mask |= 16;
+ } else {
+ FAIL();
+ }
+ }
+ ASSERT_EQ(31, mask);
+}
+
+TEST(mediametrics_tests, item_expansion) {
+ mediametrics::Item<1> item("I");
+ item.set("i32", (int32_t)1)
+ .set("i64", (int64_t)2)
+ .set("double", (double)3.125)
+ .set("string", "abcdefghijklmnopqrstuvwxyz")
+ .set("rate", std::pair<int64_t, int64_t>(11, 12));
+ ASSERT_TRUE(item.updateHeader());
+
+ MediaAnalyticsItem item2;
+ item2.readFromByteString(item.getBuffer(), item.getLength());
+ ASSERT_EQ((pid_t)-1, item2.getPid());
+ ASSERT_EQ((uid_t)-1, item2.getUid());
+ int mask = 0;
+ for (auto &prop : item2) {
+ const char *name = prop.getName();
+ if (!strcmp(name, "i32")) {
+ int32_t i32;
+ ASSERT_TRUE(prop.get(&i32));
+ ASSERT_EQ(1, i32);
+ mask |= 1;
+ } else if (!strcmp(name, "i64")) {
+ int64_t i64;
+ ASSERT_TRUE(prop.get(&i64));
+ ASSERT_EQ(2, i64);
+ mask |= 2;
+ } else if (!strcmp(name, "double")) {
+ double d;
+ ASSERT_TRUE(prop.get(&d));
+ ASSERT_EQ(3.125, d);
+ mask |= 4;
+ } else if (!strcmp(name, "string")) {
+ const char *s;
+ ASSERT_TRUE(prop.get(&s));
+ ASSERT_EQ(0, strcmp(s, "abcdefghijklmnopqrstuvwxyz"));
+ mask |= 8;
+ } else if (!strcmp(name, "rate")) {
+ std::pair<int64_t, int64_t> r;
+ ASSERT_TRUE(prop.get(&r));
+ ASSERT_EQ(11, r.first);
+ ASSERT_EQ(12, r.second);
+ mask |= 16;
+ } else {
+ FAIL();
+ }
+ }
+ ASSERT_EQ(31, mask);
+}
+
+TEST(mediametrics_tests, item_expansion2) {
+ mediametrics::Item<1> item("Bigly");
+ item.setPid(123)
+ .setUid(456);
+ constexpr size_t count = 10000;
+
+ for (size_t i = 0; i < count; ++i) {
+ // printf("recording %zu, %p, len:%zu of %zu remaining:%zu \n", i, item.getBuffer(), item.getLength(), item.getCapacity(), item.getRemaining());
+ item.set(std::to_string(i).c_str(), (int32_t)i);
+ }
+ ASSERT_TRUE(item.updateHeader());
+
+ MediaAnalyticsItem item2;
+ printf("begin buffer:%p length:%zu\n", item.getBuffer(), item.getLength());
+ fflush(stdout);
+ item2.readFromByteString(item.getBuffer(), item.getLength());
+
+ ASSERT_EQ((pid_t)123, item2.getPid());
+ ASSERT_EQ((uid_t)456, item2.getUid());
+ for (size_t i = 0; i < count; ++i) {
+ int32_t i32;
+ ASSERT_TRUE(item2.getInt32(std::to_string(i).c_str(), &i32));
+ ASSERT_EQ((int32_t)i, i32);
+ }
+}
+
+TEST(mediametrics_tests, time_machine_storage) {
+ auto item = std::make_shared<MediaAnalyticsItem>("Key");
+ (*item).set("i32", (int32_t)1)
+ .set("i64", (int64_t)2)
+ .set("double", (double)3.125)
+ .set("string", "abcdefghijklmnopqrstuvwxyz")
+ .set("rate", std::pair<int64_t, int64_t>(11, 12));
+
+ // Let's put the item in
+ android::mediametrics::TimeMachine timeMachine;
+ ASSERT_EQ(NO_ERROR, timeMachine.put(item, true));
+
+ // Can we read the values?
+ int32_t i32;
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key", "i32", &i32, -1));
+ ASSERT_EQ(1, i32);
+
+ int64_t i64;
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key", "i64", &i64, -1));
+ ASSERT_EQ(2, i64);
+
+ double d;
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key", "double", &d, -1));
+ ASSERT_EQ(3.125, d);
+
+ std::string s;
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key", "string", &s, -1));
+ ASSERT_EQ("abcdefghijklmnopqrstuvwxyz", s);
+
+ // Using fully qualified name?
+ i32 = 0;
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key.i32", &i32, -1));
+ ASSERT_EQ(1, i32);
+
+ i64 = 0;
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key.i64", &i64, -1));
+ ASSERT_EQ(2, i64);
+
+ d = 0.;
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key.double", &d, -1));
+ ASSERT_EQ(3.125, d);
+
+ s.clear();
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key.string", &s, -1));
+ ASSERT_EQ("abcdefghijklmnopqrstuvwxyz", s);
+}
+
+TEST(mediametrics_tests, time_machine_remote_key) {
+ auto item = std::make_shared<MediaAnalyticsItem>("Key1");
+ (*item).set("one", (int32_t)1)
+ .set("two", (int32_t)2);
+
+ android::mediametrics::TimeMachine timeMachine;
+ ASSERT_EQ(NO_ERROR, timeMachine.put(item, true));
+
+ auto item2 = std::make_shared<MediaAnalyticsItem>("Key2");
+ (*item2).set("three", (int32_t)3)
+ .set("[Key1]four", (int32_t)4) // affects Key1
+ .set("[Key1]five", (int32_t)5); // affects key1
+
+ ASSERT_EQ(NO_ERROR, timeMachine.put(item2, true));
+
+ auto item3 = std::make_shared<MediaAnalyticsItem>("Key2");
+ (*item3).set("six", (int32_t)6)
+ .set("[Key1]seven", (int32_t)7); // affects Key1
+
+ ASSERT_EQ(NO_ERROR, timeMachine.put(item3, false)); // remote keys not allowed.
+
+ // Can we read the values?
+ int32_t i32;
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key1.one", &i32, -1));
+ ASSERT_EQ(1, i32);
+
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key1.two", &i32, -1));
+ ASSERT_EQ(2, i32);
+
+ ASSERT_EQ(BAD_VALUE, timeMachine.get("Key1.three", &i32, -1));
+
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key2.three", &i32, -1));
+ ASSERT_EQ(3, i32);
+
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key1.four", &i32, -1));
+ ASSERT_EQ(4, i32);
+
+ ASSERT_EQ(BAD_VALUE, timeMachine.get("Key2.four", &i32, -1));
+
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key1.five", &i32, -1));
+ ASSERT_EQ(5, i32);
+
+ ASSERT_EQ(BAD_VALUE, timeMachine.get("Key2.five", &i32, -1));
+
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key2.six", &i32, -1));
+ ASSERT_EQ(6, i32);
+
+ ASSERT_EQ(BAD_VALUE, timeMachine.get("Key2.seven", &i32, -1));
+}
+
+TEST(mediametrics_tests, time_machine_gc) {
+ auto item = std::make_shared<MediaAnalyticsItem>("Key1");
+ (*item).set("one", (int32_t)1)
+ .set("two", (int32_t)2)
+ .setTimestamp(10);
+
+ android::mediametrics::TimeMachine timeMachine(1, 2); // keep at most 2 keys.
+
+ ASSERT_EQ((size_t)0, timeMachine.size());
+
+ ASSERT_EQ(NO_ERROR, timeMachine.put(item, true));
+
+ ASSERT_EQ((size_t)1, timeMachine.size());
+
+ auto item2 = std::make_shared<MediaAnalyticsItem>("Key2");
+ (*item2).set("three", (int32_t)3)
+ .set("[Key1]three", (int32_t)3)
+ .setTimestamp(11);
+
+ ASSERT_EQ(NO_ERROR, timeMachine.put(item2, true));
+ ASSERT_EQ((size_t)2, timeMachine.size());
+
+ //printf("Before\n%s\n\n", timeMachine.dump().c_str());
+
+ auto item3 = std::make_shared<MediaAnalyticsItem>("Key3");
+ (*item3).set("six", (int32_t)6)
+ .set("[Key1]four", (int32_t)4) // affects Key1
+ .set("[Key1]five", (int32_t)5) // affects key1
+ .setTimestamp(12);
+
+ ASSERT_EQ(NO_ERROR, timeMachine.put(item3, true));
+
+ ASSERT_EQ((size_t)2, timeMachine.size());
+
+ // Can we read the values?
+ int32_t i32;
+ ASSERT_EQ(BAD_VALUE, timeMachine.get("Key1.one", &i32, -1));
+ ASSERT_EQ(BAD_VALUE, timeMachine.get("Key1.two", &i32, -1));
+ ASSERT_EQ(BAD_VALUE, timeMachine.get("Key1.three", &i32, -1));
+ ASSERT_EQ(BAD_VALUE, timeMachine.get("Key1.four", &i32, -1));
+ ASSERT_EQ(BAD_VALUE, timeMachine.get("Key1.five", &i32, -1));
+
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key2.three", &i32, -1));
+ ASSERT_EQ(3, i32);
+
+ ASSERT_EQ(NO_ERROR, timeMachine.get("Key3.six", &i32, -1));
+ ASSERT_EQ(6, i32);
+
+ printf("After\n%s\n", timeMachine.dump().first.c_str());
+}
+
+TEST(mediametrics_tests, transaction_log_gc) {
+ auto item = std::make_shared<MediaAnalyticsItem>("Key1");
+ (*item).set("one", (int32_t)1)
+ .set("two", (int32_t)2)
+ .setTimestamp(10);
+
+ android::mediametrics::TransactionLog transactionLog(1, 2); // keep at most 2 items
+ ASSERT_EQ((size_t)0, transactionLog.size());
+
+ ASSERT_EQ(NO_ERROR, transactionLog.put(item));
+ ASSERT_EQ((size_t)1, transactionLog.size());
+
+ auto item2 = std::make_shared<MediaAnalyticsItem>("Key2");
+ (*item2).set("three", (int32_t)3)
+ .set("[Key1]three", (int32_t)3)
+ .setTimestamp(11);
+
+ ASSERT_EQ(NO_ERROR, transactionLog.put(item2));
+ ASSERT_EQ((size_t)2, transactionLog.size());
+
+ auto item3 = std::make_shared<MediaAnalyticsItem>("Key3");
+ (*item3).set("six", (int32_t)6)
+ .set("[Key1]four", (int32_t)4) // affects Key1
+ .set("[Key1]five", (int32_t)5) // affects key1
+ .setTimestamp(12);
+
+ ASSERT_EQ(NO_ERROR, transactionLog.put(item3));
+ ASSERT_EQ((size_t)2, transactionLog.size());
+}
+
+TEST(mediametrics_tests, audio_analytics_permission) {
+ auto item = std::make_shared<MediaAnalyticsItem>("audio.1");
+ (*item).set("one", (int32_t)1)
+ .set("two", (int32_t)2)
+ .setTimestamp(10);
+
+ auto item2 = std::make_shared<MediaAnalyticsItem>("audio.1");
+ (*item2).set("three", (int32_t)3)
+ .setTimestamp(11);
+
+ auto item3 = std::make_shared<MediaAnalyticsItem>("audio.2");
+ (*item3).set("four", (int32_t)4)
+ .setTimestamp(12);
+
+ android::mediametrics::AudioAnalytics audioAnalytics;
+
+ // untrusted entities cannot create a new key.
+ ASSERT_EQ(PERMISSION_DENIED, audioAnalytics.submit(item, false /* isTrusted */));
+ ASSERT_EQ(PERMISSION_DENIED, audioAnalytics.submit(item2, false /* isTrusted */));
+
+ // TODO: Verify contents of AudioAnalytics.
+ // Currently there is no getter API in AudioAnalytics besides dump.
+ ASSERT_EQ(4, audioAnalytics.dump(1000).second /* lines */);
+
+ ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item, true /* isTrusted */));
+ // untrusted entities can add to an existing key
+ ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item2, false /* isTrusted */));
+
+ // Check that we have some info in the dump.
+ ASSERT_LT(4, audioAnalytics.dump(1000).second /* lines */);
+}
+
+TEST(mediametrics_tests, audio_analytics_dump) {
+ auto item = std::make_shared<MediaAnalyticsItem>("audio.1");
+ (*item).set("one", (int32_t)1)
+ .set("two", (int32_t)2)
+ .setTimestamp(10);
+
+ auto item2 = std::make_shared<MediaAnalyticsItem>("audio.1");
+ (*item2).set("three", (int32_t)3)
+ .setTimestamp(11);
+
+ auto item3 = std::make_shared<MediaAnalyticsItem>("audio.2");
+ (*item3).set("four", (int32_t)4)
+ .setTimestamp(12);
+
+ android::mediametrics::AudioAnalytics audioAnalytics;
+
+ ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item, true /* isTrusted */));
+ // untrusted entities can add to an existing key
+ ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item2, false /* isTrusted */));
+ ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item3, true /* isTrusted */));
+
+ // find out how many lines we have.
+ auto [string, lines] = audioAnalytics.dump(1000);
+ ASSERT_EQ(lines, (int32_t) countNewlines(string.c_str()));
+
+ printf("AudioAnalytics: %s", string.c_str());
+ // ensure that dump operates over those lines.
+ for (int32_t ll = 0; ll < lines; ++ll) {
+ auto [s, l] = audioAnalytics.dump(ll);
+ ASSERT_EQ(ll, l);
+ ASSERT_EQ(ll, (int32_t) countNewlines(s.c_str()));
+ }
+}