Loading services/mediametrics/AnalyticsActions.h +9 −28 Original line number Diff line number Diff line Loading @@ -78,8 +78,8 @@ public: template <typename T, typename U, typename A> void addAction(T&& url, U&& value, A&& action) { std::lock_guard l(mLock); mFilters[ { std::forward<T>(url), std::forward<U>(value) } ] = std::forward<A>(action); mFilters.emplace(Trigger{ std::forward<T>(url), std::forward<U>(value) }, std::forward<A>(action)); } // TODO: remove an action. Loading @@ -94,36 +94,15 @@ public: std::vector<Action> actions; std::lock_guard l(mLock); // Essentially the code looks like this: /* for (auto &[trigger, action] : mFilters) { if (isMatch(trigger, item)) { for (const auto &[trigger, action] : mFilters) { if (isWildcardMatch(trigger, item) == mediametrics::Item::RECURSIVE_WILDCARD_CHECK_MATCH_FOUND) { actions.push_back(action); } } */ // Optimization: there should only be one match for a non-wildcard url. auto it = mFilters.upper_bound( {item->getKey(), std::monostate{} }); if (it != mFilters.end()) { const auto &[trigger, action] = *it; if (isMatch(trigger, item)) { actions.push_back(action); } } // TODO: Optimize for prefix search and wildcarding. // Optimization: for wildcard URLs we go backwards until there is no // match with the prefix before the wildcard. while (it != mFilters.begin()) { // this walks backwards, cannot start at begin. const auto &[trigger, action] = *--it; // look backwards int ret = isWildcardMatch(trigger, item); if (ret == mediametrics::Item::RECURSIVE_WILDCARD_CHECK_MATCH_FOUND) { actions.push_back(action); // match found. } else if (ret == mediametrics::Item::RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD) { break; // no match before wildcard. } // a wildcard was encountered when matching prefix, so we should check again. } return actions; } Loading @@ -145,7 +124,9 @@ private: } mutable std::mutex mLock; std::map<Trigger, Action> mFilters GUARDED_BY(mLock); using FilterType = std::multimap<Trigger, Action>; FilterType mFilters GUARDED_BY(mLock); }; } // namespace android::mediametrics services/mediametrics/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -76,5 +76,6 @@ cc_library_shared { "-Wall", "-Werror", "-Wextra", "-Wthread-safety", ], } services/mediametrics/AudioAnalytics.cpp +263 −1 Original line number Diff line number Diff line Loading @@ -19,8 +19,12 @@ #include <utils/Log.h> #include "AudioAnalytics.h" #include "MediaMetricsService.h" // package info #include <audio_utils/clock.h> // clock conversions #include <statslog.h> // statsd // Enable for testing of delivery to statsd // #define STATSD namespace android::mediametrics { Loading Loading @@ -87,11 +91,53 @@ AudioAnalytics::AudioAnalytics() // report this for Bluetooth } })); // Handle device use thread statistics mActions.addAction( AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD "*." AMEDIAMETRICS_PROP_EVENT, std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP), std::make_shared<AnalyticsActions::Function>( [this](const std::shared_ptr<const android::mediametrics::Item> &item){ mDeviceUse.endAudioIntervalGroup(item, false /* isTrack */); })); // Handle device use track statistics mActions.addAction( AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK "*." AMEDIAMETRICS_PROP_EVENT, std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP), std::make_shared<AnalyticsActions::Function>( [this](const std::shared_ptr<const android::mediametrics::Item> &item){ mDeviceUse.endAudioIntervalGroup(item, true /* isTrack */); })); // Handle device routing statistics // We track connections (not disconnections) for the time to connect. // TODO: consider BT requests in their A2dp service // AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent // AudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent // AudioDeviceBroker.postA2dpActiveDeviceChange mActions.addAction( "audio.device.a2dp.state", std::string("connected"), std::make_shared<AnalyticsActions::Function>( [this](const std::shared_ptr<const android::mediametrics::Item> &item){ mDeviceConnection.a2dpConnected(item); })); // If audio is active, we expect to see a createAudioPatch after the device is connected. mActions.addAction( AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD "*." AMEDIAMETRICS_PROP_EVENT, std::string("createAudioPatch"), std::make_shared<AnalyticsActions::Function>( [this](const std::shared_ptr<const android::mediametrics::Item> &item){ mDeviceConnection.createPatch(item); })); } AudioAnalytics::~AudioAnalytics() { ALOGD("%s", __func__); mTimedAction.quit(); // ensure no deferred access during destructor. } status_t AudioAnalytics::submit( Loading Loading @@ -151,4 +197,220 @@ std::string AudioAnalytics::getThreadFromTrack(const std::string& track) const return std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD) + std::to_string(threadId_int32); } // DeviceUse helper class. void AudioAnalytics::DeviceUse::endAudioIntervalGroup( const std::shared_ptr<const android::mediametrics::Item> &item, bool isTrack) const { const std::string& key = item->getKey(); const std::string id = key.substr( (isTrack ? sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK) : sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD)) - 1); // deliver statistics int64_t deviceTimeNs = 0; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_DEVICETIMENS, &deviceTimeNs); std::string encoding; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_ENCODING, &encoding); int32_t frameCount = 0; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_FRAMECOUNT, &frameCount); int32_t intervalCount = 0; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_INTERVALCOUNT, &intervalCount); std::string outputDevices; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices); int32_t sampleRate = 0; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_SAMPLERATE, &sampleRate); int32_t underrun = 0; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_UNDERRUN, &underrun); // Get connected device name if from bluetooth. bool isBluetooth = false; std::string name; if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) { isBluetooth = true; mAudioAnalytics.mAnalyticsState->timeMachine().get( "audio.device.bt_a2dp", AMEDIAMETRICS_PROP_NAME, &name); } // We may have several devices. We only list the first device. // TODO: consider whether we should list all the devices separated by | std::string firstDevice = "unknown"; auto devaddrvec = MediaMetricsService::getDeviceAddressPairs(outputDevices); if (devaddrvec.size() != 0) { firstDevice = devaddrvec[0].first; // DO NOT show the address. } if (isTrack) { std::string callerName; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_CALLERNAME, &callerName); std::string contentType; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_CONTENTTYPE, &contentType); double deviceLatencyMs = 0.; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_DEVICELATENCYMS, &deviceLatencyMs); double deviceStartupMs = 0.; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_DEVICESTARTUPMS, &deviceStartupMs); double deviceVolume = 0.; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_DEVICEVOLUME, &deviceVolume); std::string packageName; int64_t versionCode = 0; int32_t uid = -1; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_ALLOWUID, &uid); if (uid != -1) { std::tie(packageName, versionCode) = MediaMetricsService::getSanitizedPackageNameAndVersionCode(uid); } double playbackPitch = 0.; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_PLAYBACK_PITCH, &playbackPitch); double playbackSpeed = 0.; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_PLAYBACK_SPEED, &playbackSpeed); int32_t selectedDeviceId = 0; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_SELECTEDDEVICEID, &selectedDeviceId); std::string usage; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_USAGE, &usage); ALOGD("(key=%s) id:%s endAudioIntervalGroup device:%s name:%s " "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d " "sampleRate:%d underrun:%d " "callerName:%s contentType:%s " "deviceLatencyMs:%lf deviceStartupMs:%lf deviceVolume:%lf" "packageName:%s playbackPitch:%lf playbackSpeed:%lf " "selectedDevceId:%d usage:%s", key.c_str(), id.c_str(), firstDevice.c_str(), name.c_str(), (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount, sampleRate, underrun, callerName.c_str(), contentType.c_str(), deviceLatencyMs, deviceStartupMs, deviceVolume, packageName.c_str(), playbackPitch, playbackSpeed, selectedDeviceId, usage.c_str()); #ifdef STATSD if (mAudioAnalytics.mDeliverStatistics) { (void)android::util::stats_write( android::util::MEDIAMETRICS_AUDIOTRACKDEVICEUSAGE_REPORTED /* timestamp, */ /* mediaApexVersion, */ , firstDevice.c_str() , name.c_str() , deviceTimeNs , encoding.c_str() , frameCount , intervalCount , sampleRate , underrun , packageName.c_str() , (float)deviceLatencyMs , (float)deviceStartupMs , (float)deviceVolume , selectedDeviceId , usage.c_str() , contentType.c_str() , callerName.c_str() ); } #endif } else { std::string flags; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_FLAGS, &flags); ALOGD("(key=%s) id:%s endAudioIntervalGroup device:%s name:%s " "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d " "sampleRate:%d underrun:%d " "flags:%s", key.c_str(), id.c_str(), firstDevice.c_str(), name.c_str(), (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount, sampleRate, underrun, flags.c_str()); #ifdef STATSD if (mAudioAnalytics.mDeliverStatistics) { (void)android::util::stats_write( android::util::MEDIAMETRICS_AUDIOTHREADDEVICEUSAGE_REPORTED /* timestamp, */ /* mediaApexVersion, */ , firstDevice.c_str() , name.c_str() , deviceTimeNs , encoding.c_str() , frameCount , intervalCount , sampleRate , underrun ); } #endif } // Report this as needed. if (isBluetooth) { // report this for Bluetooth } } // DeviceConnection helper class. void AudioAnalytics::DeviceConnection::a2dpConnected( const std::shared_ptr<const android::mediametrics::Item> &item) { const std::string& key = item->getKey(); const int64_t connectedAtNs = item->getTimestamp(); { std::lock_guard l(mLock); mA2dpTimeConnectedNs = connectedAtNs; ++mA2dpConnectedAttempts; } std::string name; item->get(AMEDIAMETRICS_PROP_NAME, &name); ALOGD("(key=%s) a2dp connected device:%s " "connectedAtNs:%lld", key.c_str(), name.c_str(), (long long)connectedAtNs); // Note - we need to be able to cancel a timed event mAudioAnalytics.mTimedAction.postIn(std::chrono::seconds(5), [this](){ expire(); }); // This sets the time we were connected. Now we look for the delta in the future. } void AudioAnalytics::DeviceConnection::createPatch( const std::shared_ptr<const android::mediametrics::Item> &item) { std::lock_guard l(mLock); if (mA2dpTimeConnectedNs == 0) return; // ignore const std::string& key = item->getKey(); std::string outputDevices; item->get(AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices); if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) { // TODO compare address const int64_t timeDiff = item->getTimestamp() - mA2dpTimeConnectedNs; ALOGD("(key=%s) A2DP device connection time: %lld", key.c_str(), (long long)timeDiff); mA2dpTimeConnectedNs = 0; // reset counter. ++mA2dpConnectedSuccesses; } } void AudioAnalytics::DeviceConnection::expire() { std::lock_guard l(mLock); if (mA2dpTimeConnectedNs == 0) return; // ignore // An expiration may occur because there is no audio playing. // TODO: disambiguate this case. ALOGD("A2DP device connection expired"); ++mA2dpConnectedFailures; // this is not a true failure. mA2dpTimeConnectedNs = 0; } } // namespace android services/mediametrics/AudioAnalytics.h +52 −0 Original line number Diff line number Diff line Loading @@ -16,8 +16,10 @@ #pragma once #include <android-base/thread_annotations.h> #include "AnalyticsActions.h" #include "AnalyticsState.h" #include "TimedAction.h" #include "Wrap.h" namespace android::mediametrics { Loading Loading @@ -89,6 +91,8 @@ private: */ std::string getThreadFromTrack(const std::string& track) const; const bool mDeliverStatistics __unused = true; // Actions is individually locked AnalyticsActions mActions; Loading @@ -97,6 +101,54 @@ private: SharedPtrWrap<AnalyticsState> mAnalyticsState; SharedPtrWrap<AnalyticsState> mPreviousAnalyticsState; TimedAction mTimedAction; // locked internally // DeviceUse is a nested class which handles audio device usage accounting. // We define this class at the end to ensure prior variables all properly constructed. // TODO: Track / Thread interaction // TODO: Consider statistics aggregation. class DeviceUse { public: explicit DeviceUse(AudioAnalytics &audioAnalytics) : mAudioAnalytics{audioAnalytics} {} // Called every time an endAudioIntervalGroup message is received. void endAudioIntervalGroup( const std::shared_ptr<const android::mediametrics::Item> &item, bool isTrack) const; private: AudioAnalytics &mAudioAnalytics; } mDeviceUse{*this}; // DeviceConnected is a nested class which handles audio device connection // We define this class at the end to ensure prior variables all properly constructed. // TODO: Track / Thread interaction // TODO: Consider statistics aggregation. class DeviceConnection { public: explicit DeviceConnection(AudioAnalytics &audioAnalytics) : mAudioAnalytics{audioAnalytics} {} // Called every time an endAudioIntervalGroup message is received. void a2dpConnected( const std::shared_ptr<const android::mediametrics::Item> &item); // Called when we have an AudioFlinger createPatch void createPatch( const std::shared_ptr<const android::mediametrics::Item> &item); // When the timer expires. void expire(); private: AudioAnalytics &mAudioAnalytics; mutable std::mutex mLock; int64_t mA2dpTimeConnectedNs GUARDED_BY(mLock) = 0; int32_t mA2dpConnectedAttempts GUARDED_BY(mLock) = 0; int32_t mA2dpConnectedSuccesses GUARDED_BY(mLock) = 0; int32_t mA2dpConnectedFailures GUARDED_BY(mLock) = 0; } mDeviceConnection{*this}; }; } // namespace android::mediametrics services/mediametrics/MediaMetricsService.cpp +72 −10 Original line number Diff line number Diff line Loading @@ -78,6 +78,74 @@ bool MediaMetricsService::useUidForPackage( } } /* static */ std::pair<std::string, int64_t> MediaMetricsService::getSanitizedPackageNameAndVersionCode(uid_t uid) { // Meyer's singleton, initialized on first access. // mUidInfo is locked internally. static mediautils::UidInfo uidInfo; // get info. mediautils::UidInfo::Info info = uidInfo.getInfo(uid); if (useUidForPackage(info.package, info.installer)) { return { std::to_string(uid), /* versionCode */ 0 }; } else { return { info.package, info.versionCode }; } } /* static */ std::string MediaMetricsService::tokenizer(std::string::const_iterator& it, const std::string::const_iterator& end, const char *reserved) { // consume leading white space for (; it != end && std::isspace(*it); ++it); if (it == end) return {}; auto start = it; // parse until we hit a reserved keyword or space if (strchr(reserved, *it)) return {start, ++it}; for (;;) { ++it; if (it == end || std::isspace(*it) || strchr(reserved, *it)) return {start, it}; } } /* static */ std::vector<std::pair<std::string, std::string>> MediaMetricsService::getDeviceAddressPairs(const std::string& devices) { std::vector<std::pair<std::string, std::string>> result; // Currently, the device format is EXACTLY // (device1, addr1)|(device2, addr2)|... static constexpr char delim[] = "()|,"; for (auto it = devices.begin(); ; ) { auto token = tokenizer(it, devices.end(), delim); if (token != "(") return result; auto device = tokenizer(it, devices.end(), delim); if (device.empty() || !std::isalnum(device[0])) return result; token = tokenizer(it, devices.end(), delim); if (token != ",") return result; // special handling here for empty addresses auto address = tokenizer(it, devices.end(), delim); if (address.empty() || !std::isalnum(device[0])) return result; if (address == ")") { // no address, just the ")" address.clear(); } else { token = tokenizer(it, devices.end(), delim); if (token != ")") return result; } result.emplace_back(std::move(device), std::move(address)); token = tokenizer(it, devices.end(), delim); if (token != "|") return result; // this includes end of string detection } } MediaMetricsService::MediaMetricsService() : mMaxRecords(kMaxRecords), mMaxRecordAgeNs(kMaxRecordAgeNs), Loading Loading @@ -136,16 +204,10 @@ status_t MediaMetricsService::submitInternal(mediametrics::Item *item, bool rele // Overwrite package name and version if the caller was untrusted or empty if (!isTrusted || item->getPkgName().empty()) { const uid_t uid = item->getUid(); mediautils::UidInfo::Info info = mUidInfo.getInfo(uid); if (useUidForPackage(info.package, info.installer)) { // remove uid information of unknown installed packages. // TODO: perhaps this can be done just before uploading to Westworld. item->setPkgName(std::to_string(uid)); item->setPkgVersionCode(0); } else { item->setPkgName(info.package); item->setPkgVersionCode(info.versionCode); } const auto [ pkgName, version ] = MediaMetricsService::getSanitizedPackageNameAndVersionCode(uid); item->setPkgName(pkgName); item->setPkgVersionCode(version); } ALOGV("%s: isTrusted:%d given uid %d; sanitized uid: %d sanitized pkg: %s " Loading Loading
services/mediametrics/AnalyticsActions.h +9 −28 Original line number Diff line number Diff line Loading @@ -78,8 +78,8 @@ public: template <typename T, typename U, typename A> void addAction(T&& url, U&& value, A&& action) { std::lock_guard l(mLock); mFilters[ { std::forward<T>(url), std::forward<U>(value) } ] = std::forward<A>(action); mFilters.emplace(Trigger{ std::forward<T>(url), std::forward<U>(value) }, std::forward<A>(action)); } // TODO: remove an action. Loading @@ -94,36 +94,15 @@ public: std::vector<Action> actions; std::lock_guard l(mLock); // Essentially the code looks like this: /* for (auto &[trigger, action] : mFilters) { if (isMatch(trigger, item)) { for (const auto &[trigger, action] : mFilters) { if (isWildcardMatch(trigger, item) == mediametrics::Item::RECURSIVE_WILDCARD_CHECK_MATCH_FOUND) { actions.push_back(action); } } */ // Optimization: there should only be one match for a non-wildcard url. auto it = mFilters.upper_bound( {item->getKey(), std::monostate{} }); if (it != mFilters.end()) { const auto &[trigger, action] = *it; if (isMatch(trigger, item)) { actions.push_back(action); } } // TODO: Optimize for prefix search and wildcarding. // Optimization: for wildcard URLs we go backwards until there is no // match with the prefix before the wildcard. while (it != mFilters.begin()) { // this walks backwards, cannot start at begin. const auto &[trigger, action] = *--it; // look backwards int ret = isWildcardMatch(trigger, item); if (ret == mediametrics::Item::RECURSIVE_WILDCARD_CHECK_MATCH_FOUND) { actions.push_back(action); // match found. } else if (ret == mediametrics::Item::RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD) { break; // no match before wildcard. } // a wildcard was encountered when matching prefix, so we should check again. } return actions; } Loading @@ -145,7 +124,9 @@ private: } mutable std::mutex mLock; std::map<Trigger, Action> mFilters GUARDED_BY(mLock); using FilterType = std::multimap<Trigger, Action>; FilterType mFilters GUARDED_BY(mLock); }; } // namespace android::mediametrics
services/mediametrics/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -76,5 +76,6 @@ cc_library_shared { "-Wall", "-Werror", "-Wextra", "-Wthread-safety", ], }
services/mediametrics/AudioAnalytics.cpp +263 −1 Original line number Diff line number Diff line Loading @@ -19,8 +19,12 @@ #include <utils/Log.h> #include "AudioAnalytics.h" #include "MediaMetricsService.h" // package info #include <audio_utils/clock.h> // clock conversions #include <statslog.h> // statsd // Enable for testing of delivery to statsd // #define STATSD namespace android::mediametrics { Loading Loading @@ -87,11 +91,53 @@ AudioAnalytics::AudioAnalytics() // report this for Bluetooth } })); // Handle device use thread statistics mActions.addAction( AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD "*." AMEDIAMETRICS_PROP_EVENT, std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP), std::make_shared<AnalyticsActions::Function>( [this](const std::shared_ptr<const android::mediametrics::Item> &item){ mDeviceUse.endAudioIntervalGroup(item, false /* isTrack */); })); // Handle device use track statistics mActions.addAction( AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK "*." AMEDIAMETRICS_PROP_EVENT, std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP), std::make_shared<AnalyticsActions::Function>( [this](const std::shared_ptr<const android::mediametrics::Item> &item){ mDeviceUse.endAudioIntervalGroup(item, true /* isTrack */); })); // Handle device routing statistics // We track connections (not disconnections) for the time to connect. // TODO: consider BT requests in their A2dp service // AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent // AudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent // AudioDeviceBroker.postA2dpActiveDeviceChange mActions.addAction( "audio.device.a2dp.state", std::string("connected"), std::make_shared<AnalyticsActions::Function>( [this](const std::shared_ptr<const android::mediametrics::Item> &item){ mDeviceConnection.a2dpConnected(item); })); // If audio is active, we expect to see a createAudioPatch after the device is connected. mActions.addAction( AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD "*." AMEDIAMETRICS_PROP_EVENT, std::string("createAudioPatch"), std::make_shared<AnalyticsActions::Function>( [this](const std::shared_ptr<const android::mediametrics::Item> &item){ mDeviceConnection.createPatch(item); })); } AudioAnalytics::~AudioAnalytics() { ALOGD("%s", __func__); mTimedAction.quit(); // ensure no deferred access during destructor. } status_t AudioAnalytics::submit( Loading Loading @@ -151,4 +197,220 @@ std::string AudioAnalytics::getThreadFromTrack(const std::string& track) const return std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD) + std::to_string(threadId_int32); } // DeviceUse helper class. void AudioAnalytics::DeviceUse::endAudioIntervalGroup( const std::shared_ptr<const android::mediametrics::Item> &item, bool isTrack) const { const std::string& key = item->getKey(); const std::string id = key.substr( (isTrack ? sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK) : sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD)) - 1); // deliver statistics int64_t deviceTimeNs = 0; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_DEVICETIMENS, &deviceTimeNs); std::string encoding; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_ENCODING, &encoding); int32_t frameCount = 0; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_FRAMECOUNT, &frameCount); int32_t intervalCount = 0; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_INTERVALCOUNT, &intervalCount); std::string outputDevices; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices); int32_t sampleRate = 0; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_SAMPLERATE, &sampleRate); int32_t underrun = 0; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_UNDERRUN, &underrun); // Get connected device name if from bluetooth. bool isBluetooth = false; std::string name; if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) { isBluetooth = true; mAudioAnalytics.mAnalyticsState->timeMachine().get( "audio.device.bt_a2dp", AMEDIAMETRICS_PROP_NAME, &name); } // We may have several devices. We only list the first device. // TODO: consider whether we should list all the devices separated by | std::string firstDevice = "unknown"; auto devaddrvec = MediaMetricsService::getDeviceAddressPairs(outputDevices); if (devaddrvec.size() != 0) { firstDevice = devaddrvec[0].first; // DO NOT show the address. } if (isTrack) { std::string callerName; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_CALLERNAME, &callerName); std::string contentType; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_CONTENTTYPE, &contentType); double deviceLatencyMs = 0.; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_DEVICELATENCYMS, &deviceLatencyMs); double deviceStartupMs = 0.; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_DEVICESTARTUPMS, &deviceStartupMs); double deviceVolume = 0.; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_DEVICEVOLUME, &deviceVolume); std::string packageName; int64_t versionCode = 0; int32_t uid = -1; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_ALLOWUID, &uid); if (uid != -1) { std::tie(packageName, versionCode) = MediaMetricsService::getSanitizedPackageNameAndVersionCode(uid); } double playbackPitch = 0.; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_PLAYBACK_PITCH, &playbackPitch); double playbackSpeed = 0.; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_PLAYBACK_SPEED, &playbackSpeed); int32_t selectedDeviceId = 0; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_SELECTEDDEVICEID, &selectedDeviceId); std::string usage; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_USAGE, &usage); ALOGD("(key=%s) id:%s endAudioIntervalGroup device:%s name:%s " "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d " "sampleRate:%d underrun:%d " "callerName:%s contentType:%s " "deviceLatencyMs:%lf deviceStartupMs:%lf deviceVolume:%lf" "packageName:%s playbackPitch:%lf playbackSpeed:%lf " "selectedDevceId:%d usage:%s", key.c_str(), id.c_str(), firstDevice.c_str(), name.c_str(), (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount, sampleRate, underrun, callerName.c_str(), contentType.c_str(), deviceLatencyMs, deviceStartupMs, deviceVolume, packageName.c_str(), playbackPitch, playbackSpeed, selectedDeviceId, usage.c_str()); #ifdef STATSD if (mAudioAnalytics.mDeliverStatistics) { (void)android::util::stats_write( android::util::MEDIAMETRICS_AUDIOTRACKDEVICEUSAGE_REPORTED /* timestamp, */ /* mediaApexVersion, */ , firstDevice.c_str() , name.c_str() , deviceTimeNs , encoding.c_str() , frameCount , intervalCount , sampleRate , underrun , packageName.c_str() , (float)deviceLatencyMs , (float)deviceStartupMs , (float)deviceVolume , selectedDeviceId , usage.c_str() , contentType.c_str() , callerName.c_str() ); } #endif } else { std::string flags; mAudioAnalytics.mAnalyticsState->timeMachine().get( key, AMEDIAMETRICS_PROP_FLAGS, &flags); ALOGD("(key=%s) id:%s endAudioIntervalGroup device:%s name:%s " "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d " "sampleRate:%d underrun:%d " "flags:%s", key.c_str(), id.c_str(), firstDevice.c_str(), name.c_str(), (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount, sampleRate, underrun, flags.c_str()); #ifdef STATSD if (mAudioAnalytics.mDeliverStatistics) { (void)android::util::stats_write( android::util::MEDIAMETRICS_AUDIOTHREADDEVICEUSAGE_REPORTED /* timestamp, */ /* mediaApexVersion, */ , firstDevice.c_str() , name.c_str() , deviceTimeNs , encoding.c_str() , frameCount , intervalCount , sampleRate , underrun ); } #endif } // Report this as needed. if (isBluetooth) { // report this for Bluetooth } } // DeviceConnection helper class. void AudioAnalytics::DeviceConnection::a2dpConnected( const std::shared_ptr<const android::mediametrics::Item> &item) { const std::string& key = item->getKey(); const int64_t connectedAtNs = item->getTimestamp(); { std::lock_guard l(mLock); mA2dpTimeConnectedNs = connectedAtNs; ++mA2dpConnectedAttempts; } std::string name; item->get(AMEDIAMETRICS_PROP_NAME, &name); ALOGD("(key=%s) a2dp connected device:%s " "connectedAtNs:%lld", key.c_str(), name.c_str(), (long long)connectedAtNs); // Note - we need to be able to cancel a timed event mAudioAnalytics.mTimedAction.postIn(std::chrono::seconds(5), [this](){ expire(); }); // This sets the time we were connected. Now we look for the delta in the future. } void AudioAnalytics::DeviceConnection::createPatch( const std::shared_ptr<const android::mediametrics::Item> &item) { std::lock_guard l(mLock); if (mA2dpTimeConnectedNs == 0) return; // ignore const std::string& key = item->getKey(); std::string outputDevices; item->get(AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices); if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) { // TODO compare address const int64_t timeDiff = item->getTimestamp() - mA2dpTimeConnectedNs; ALOGD("(key=%s) A2DP device connection time: %lld", key.c_str(), (long long)timeDiff); mA2dpTimeConnectedNs = 0; // reset counter. ++mA2dpConnectedSuccesses; } } void AudioAnalytics::DeviceConnection::expire() { std::lock_guard l(mLock); if (mA2dpTimeConnectedNs == 0) return; // ignore // An expiration may occur because there is no audio playing. // TODO: disambiguate this case. ALOGD("A2DP device connection expired"); ++mA2dpConnectedFailures; // this is not a true failure. mA2dpTimeConnectedNs = 0; } } // namespace android
services/mediametrics/AudioAnalytics.h +52 −0 Original line number Diff line number Diff line Loading @@ -16,8 +16,10 @@ #pragma once #include <android-base/thread_annotations.h> #include "AnalyticsActions.h" #include "AnalyticsState.h" #include "TimedAction.h" #include "Wrap.h" namespace android::mediametrics { Loading Loading @@ -89,6 +91,8 @@ private: */ std::string getThreadFromTrack(const std::string& track) const; const bool mDeliverStatistics __unused = true; // Actions is individually locked AnalyticsActions mActions; Loading @@ -97,6 +101,54 @@ private: SharedPtrWrap<AnalyticsState> mAnalyticsState; SharedPtrWrap<AnalyticsState> mPreviousAnalyticsState; TimedAction mTimedAction; // locked internally // DeviceUse is a nested class which handles audio device usage accounting. // We define this class at the end to ensure prior variables all properly constructed. // TODO: Track / Thread interaction // TODO: Consider statistics aggregation. class DeviceUse { public: explicit DeviceUse(AudioAnalytics &audioAnalytics) : mAudioAnalytics{audioAnalytics} {} // Called every time an endAudioIntervalGroup message is received. void endAudioIntervalGroup( const std::shared_ptr<const android::mediametrics::Item> &item, bool isTrack) const; private: AudioAnalytics &mAudioAnalytics; } mDeviceUse{*this}; // DeviceConnected is a nested class which handles audio device connection // We define this class at the end to ensure prior variables all properly constructed. // TODO: Track / Thread interaction // TODO: Consider statistics aggregation. class DeviceConnection { public: explicit DeviceConnection(AudioAnalytics &audioAnalytics) : mAudioAnalytics{audioAnalytics} {} // Called every time an endAudioIntervalGroup message is received. void a2dpConnected( const std::shared_ptr<const android::mediametrics::Item> &item); // Called when we have an AudioFlinger createPatch void createPatch( const std::shared_ptr<const android::mediametrics::Item> &item); // When the timer expires. void expire(); private: AudioAnalytics &mAudioAnalytics; mutable std::mutex mLock; int64_t mA2dpTimeConnectedNs GUARDED_BY(mLock) = 0; int32_t mA2dpConnectedAttempts GUARDED_BY(mLock) = 0; int32_t mA2dpConnectedSuccesses GUARDED_BY(mLock) = 0; int32_t mA2dpConnectedFailures GUARDED_BY(mLock) = 0; } mDeviceConnection{*this}; }; } // namespace android::mediametrics
services/mediametrics/MediaMetricsService.cpp +72 −10 Original line number Diff line number Diff line Loading @@ -78,6 +78,74 @@ bool MediaMetricsService::useUidForPackage( } } /* static */ std::pair<std::string, int64_t> MediaMetricsService::getSanitizedPackageNameAndVersionCode(uid_t uid) { // Meyer's singleton, initialized on first access. // mUidInfo is locked internally. static mediautils::UidInfo uidInfo; // get info. mediautils::UidInfo::Info info = uidInfo.getInfo(uid); if (useUidForPackage(info.package, info.installer)) { return { std::to_string(uid), /* versionCode */ 0 }; } else { return { info.package, info.versionCode }; } } /* static */ std::string MediaMetricsService::tokenizer(std::string::const_iterator& it, const std::string::const_iterator& end, const char *reserved) { // consume leading white space for (; it != end && std::isspace(*it); ++it); if (it == end) return {}; auto start = it; // parse until we hit a reserved keyword or space if (strchr(reserved, *it)) return {start, ++it}; for (;;) { ++it; if (it == end || std::isspace(*it) || strchr(reserved, *it)) return {start, it}; } } /* static */ std::vector<std::pair<std::string, std::string>> MediaMetricsService::getDeviceAddressPairs(const std::string& devices) { std::vector<std::pair<std::string, std::string>> result; // Currently, the device format is EXACTLY // (device1, addr1)|(device2, addr2)|... static constexpr char delim[] = "()|,"; for (auto it = devices.begin(); ; ) { auto token = tokenizer(it, devices.end(), delim); if (token != "(") return result; auto device = tokenizer(it, devices.end(), delim); if (device.empty() || !std::isalnum(device[0])) return result; token = tokenizer(it, devices.end(), delim); if (token != ",") return result; // special handling here for empty addresses auto address = tokenizer(it, devices.end(), delim); if (address.empty() || !std::isalnum(device[0])) return result; if (address == ")") { // no address, just the ")" address.clear(); } else { token = tokenizer(it, devices.end(), delim); if (token != ")") return result; } result.emplace_back(std::move(device), std::move(address)); token = tokenizer(it, devices.end(), delim); if (token != "|") return result; // this includes end of string detection } } MediaMetricsService::MediaMetricsService() : mMaxRecords(kMaxRecords), mMaxRecordAgeNs(kMaxRecordAgeNs), Loading Loading @@ -136,16 +204,10 @@ status_t MediaMetricsService::submitInternal(mediametrics::Item *item, bool rele // Overwrite package name and version if the caller was untrusted or empty if (!isTrusted || item->getPkgName().empty()) { const uid_t uid = item->getUid(); mediautils::UidInfo::Info info = mUidInfo.getInfo(uid); if (useUidForPackage(info.package, info.installer)) { // remove uid information of unknown installed packages. // TODO: perhaps this can be done just before uploading to Westworld. item->setPkgName(std::to_string(uid)); item->setPkgVersionCode(0); } else { item->setPkgName(info.package); item->setPkgVersionCode(info.versionCode); } const auto [ pkgName, version ] = MediaMetricsService::getSanitizedPackageNameAndVersionCode(uid); item->setPkgName(pkgName); item->setPkgVersionCode(version); } ALOGV("%s: isTrusted:%d given uid %d; sanitized uid: %d sanitized pkg: %s " Loading