MediaMetrics: Update and clean proto formatting
Make code compatible with both string
and integer enumeration in the protos.
Test: adb shell dumpsys media.metrics
Test: atest mediametrics_tests
Bug: 149850236
Change-Id: I0a1c21be711f82915f3bb403860b40c695a0587d
diff --git a/services/mediametrics/Android.bp b/services/mediametrics/Android.bp
index 645d151..f819f1b 100644
--- a/services/mediametrics/Android.bp
+++ b/services/mediametrics/Android.bp
@@ -119,6 +119,7 @@
srcs: [
"AudioAnalytics.cpp",
"AudioPowerUsage.cpp",
+ "AudioTypes.cpp",
"iface_statsd.cpp",
"MediaMetricsService.cpp",
"statsd_audiopolicy.cpp",
@@ -130,6 +131,7 @@
"statsd_extractor.cpp",
"statsd_nuplayer.cpp",
"statsd_recorder.cpp",
+ "StringUtils.cpp"
],
proto: {
@@ -137,9 +139,11 @@
},
shared_libs: [
+ "libbase", // android logging
"libbinder",
"libcutils",
"liblog",
+ "libmedia_helper",
"libmediametrics",
"libmediautils",
"libmemunreachable",
diff --git a/services/mediametrics/AudioAnalytics.cpp b/services/mediametrics/AudioAnalytics.cpp
index 31ad234..800f099 100644
--- a/services/mediametrics/AudioAnalytics.cpp
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -16,20 +16,45 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "AudioAnalytics"
+#include <android-base/logging.h>
#include <utils/Log.h>
#include "AudioAnalytics.h"
-#include "MediaMetricsService.h" // package info
+
#include <audio_utils/clock.h> // clock conversions
+#include <cutils/properties.h>
#include <statslog.h> // statsd
+#include "AudioTypes.h" // string to int conversions
+#include "MediaMetricsService.h" // package info
+#include "StringUtils.h"
+
+#define PROP_AUDIO_ANALYTICS_CLOUD_ENABLED "persist.audio.analytics.cloud.enabled"
+
// Enable for testing of delivery to statsd
-// #define STATSD
+//#define STATSD
+
+// Transmit to statsd in integer or strings
+//#define USE_INT
+
+#ifdef USE_INT
+using short_enum_type_t = int32_t;
+using long_enum_type_t = int64_t;
+#define ENUM_EXTRACT(x) (x)
+#else
+using short_enum_type_t = std::string;
+using long_enum_type_t = std::string;
+#define ENUM_EXTRACT(x) (x).c_str()
+#endif
+
+using android::base::DEBUG;
namespace android::mediametrics {
AudioAnalytics::AudioAnalytics()
+ : mDeliverStatistics(property_get_bool(PROP_AUDIO_ANALYTICS_CLOUD_ENABLED, true))
{
+ SetMinimumLogSeverity(DEBUG); // for LOG().
ALOGD("%s", __func__);
// Add action to save AnalyticsState if audioserver is restarted.
@@ -243,33 +268,47 @@
int32_t frameCount = 0;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_FRAMECOUNT, &frameCount);
- std::string inputDevices;
+ std::string inputDevicePairs;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
- key, AMEDIAMETRICS_PROP_INPUTDEVICES, &inputDevices);
+ key, AMEDIAMETRICS_PROP_INPUTDEVICES, &inputDevicePairs);
int32_t intervalCount = 0;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_INTERVALCOUNT, &intervalCount);
- std::string outputDevices;
+ std::string outputDevicePairs;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
- key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
+ key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevicePairs);
int32_t sampleRate = 0;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_SAMPLERATE, &sampleRate);
std::string flags;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_FLAGS, &flags);
+
// We may have several devices.
- // Strings allow us to mix input and output devices together.
- // TODO: review if we want to separate them.
- std::stringstream ss;
- for (const auto& devicePairs : { outputDevices, inputDevices }) {
- const auto devaddrvec = MediaMetricsService::getDeviceAddressPairs(devicePairs);
+ // Accumulate the bit flags for input and output devices.
+ std::stringstream oss;
+ long_enum_type_t outputDeviceBits{};
+ { // compute outputDevices
+ const auto devaddrvec = stringutils::getDeviceAddressPairs(outputDevicePairs);
for (const auto& [device, addr] : devaddrvec) {
- if (ss.tellp() > 0) ss << "|"; // delimit devices with '|'.
- ss << device;
+ if (oss.tellp() > 0) oss << "|"; // delimit devices with '|'.
+ oss << device;
+ outputDeviceBits += types::lookup<types::OUTPUT_DEVICE, long_enum_type_t>(device);
}
}
- std::string devices = ss.str();
+ const std::string outputDevices = oss.str();
+
+ std::stringstream iss;
+ long_enum_type_t inputDeviceBits{};
+ { // compute inputDevices
+ const auto devaddrvec = stringutils::getDeviceAddressPairs(inputDevicePairs);
+ for (const auto& [device, addr] : devaddrvec) {
+ if (iss.tellp() > 0) iss << "|"; // delimit devices with '|'.
+ iss << device;
+ inputDeviceBits += types::lookup<types::INPUT_DEVICE, long_enum_type_t>(device);
+ }
+ }
+ const std::string inputDevices = iss.str();
// Get connected device name if from bluetooth.
bool isBluetooth = false;
@@ -278,8 +317,8 @@
isBluetooth = true;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
"audio.device.bt_a2dp", AMEDIAMETRICS_PROP_NAME, &deviceNames);
- // We don't check if deviceName is sanitized.
- // TODO: remove reserved chars such as '|' and replace with a char like '_'.
+ // Remove | if present
+ stringutils::replace(deviceNames, "|", '?');
}
switch (itemType) {
@@ -305,37 +344,43 @@
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_SOURCE, &source);
- ALOGD("(key=%s) id:%s endAudioIntervalGroup devices:%s deviceNames:%s "
- "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d "
- "sampleRate:%d "
- "packageName:%s "
- "selectedDeviceId:%d "
- "callerName:%s source:%s",
- key.c_str(), id.c_str(), devices.c_str(), deviceNames.c_str(),
- (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount,
- sampleRate,
- packageName.c_str(), selectedDeviceId,
- callerName.c_str(), source.c_str());
+ const auto callerNameForStats =
+ types::lookup<types::CALLER_NAME, short_enum_type_t>(callerName);
+ const auto encodingForStats = types::lookup<types::ENCODING, short_enum_type_t>(encoding);
+ const auto flagsForStats = types::lookup<types::INPUT_FLAG, short_enum_type_t>(flags);
+ const auto sourceForStats = types::lookup<types::SOURCE_TYPE, short_enum_type_t>(source);
+ LOG(DEBUG) << "key:" << key
+ << " id:" << id
+ << " inputDevices:" << inputDevices << "(" << inputDeviceBits
+ << ") deviceNames:" << deviceNames
+ << " deviceTimeNs:" << deviceTimeNs
+ << " encoding:" << encoding << "(" << encodingForStats
+ << ") frameCount:" << frameCount
+ << " intervalCount:" << intervalCount
+ << " sampleRate:" << sampleRate
+ << " flags:" << flags << "(" << flagsForStats
+ << ") packageName:" << packageName
+ << " selectedDeviceId:" << selectedDeviceId
+ << " callerName:" << callerName << "(" << callerNameForStats
+ << ") source:" << source << "(" << sourceForStats << ")";
#ifdef STATSD
if (mAudioAnalytics.mDeliverStatistics) {
(void)android::util::stats_write(
android::util::MEDIAMETRICS_AUDIORECORDDEVICEUSAGE_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , devices.c_str()
+ , ENUM_EXTRACT(inputDeviceBits)
, deviceNames.c_str()
, deviceTimeNs
- , encoding.c_str()
+ , ENUM_EXTRACT(encodingForStats)
, frameCount
, intervalCount
, sampleRate
- , flags.c_str()
+ , ENUM_EXTRACT(flagsForStats)
, packageName.c_str()
, selectedDeviceId
- , callerName.c_str()
- , source.c_str()
+ , ENUM_EXTRACT(callerNameForStats)
+ , ENUM_EXTRACT(sourceForStats)
);
}
#endif
@@ -347,31 +392,43 @@
int32_t underrun = 0; // zero for record types
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_UNDERRUN, &underrun);
- ALOGD("(key=%s) id:%s endAudioIntervalGroup devices:%s deviceNames:%s "
- "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d "
- "sampleRate:%d underrun:%d "
- "flags:%s type:%s",
- key.c_str(), id.c_str(), devices.c_str(), deviceNames.c_str(),
- (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount,
- sampleRate, underrun,
- flags.c_str(), type.c_str());
+
+ const bool isInput = types::isInputThreadType(type);
+ const auto encodingForStats = types::lookup<types::ENCODING, short_enum_type_t>(encoding);
+ const auto flagsForStats =
+ (isInput ? types::lookup<types::INPUT_FLAG, short_enum_type_t>(flags)
+ : types::lookup<types::OUTPUT_FLAG, short_enum_type_t>(flags));
+ const auto typeForStats = types::lookup<types::THREAD_TYPE, short_enum_type_t>(type);
+
+ LOG(DEBUG) << "key:" << key
+ << " id:" << id
+ << " inputDevices:" << inputDevices << "(" << inputDeviceBits
+ << ") outputDevices:" << outputDevices << "(" << outputDeviceBits
+ << ") deviceNames:" << deviceNames
+ << " deviceTimeNs:" << deviceTimeNs
+ << " encoding:" << encoding << "(" << encodingForStats
+ << ") frameCount:" << frameCount
+ << " intervalCount:" << intervalCount
+ << " sampleRate:" << sampleRate
+ << " underrun:" << underrun
+ << " flags:" << flags << "(" << flagsForStats
+ << ") type:" << type << "(" << typeForStats
+ << ")";
#ifdef STATSD
if (mAudioAnalytics.mDeliverStatistics) {
(void)android::util::stats_write(
android::util::MEDIAMETRICS_AUDIOTHREADDEVICEUSAGE_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , devices.c_str()
+ , ENUM_EXTRACT(inputDeviceBits)
+ , ENUM_EXTRACT(outputDeviceBits)
, deviceNames.c_str()
, deviceTimeNs
- , encoding.c_str()
+ , ENUM_EXTRACT(encodingForStats)
, frameCount
, intervalCount
, sampleRate
- , flags.c_str()
-
+ , ENUM_EXTRACT(flagsForStats)
, underrun
- , type.c_str()
+ , ENUM_EXTRACT(typeForStats)
);
}
#endif
@@ -420,34 +477,51 @@
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_USAGE, &usage);
- ALOGD("(key=%s) id:%s endAudioIntervalGroup devices:%s deviceNames:%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 "
- "selectedDeviceId:%d streamType:%s usage:%s",
- key.c_str(), id.c_str(), devices.c_str(), deviceNames.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, streamType.c_str(), usage.c_str());
+ const auto callerNameForStats =
+ types::lookup<types::CALLER_NAME, short_enum_type_t>(callerName);
+ const auto contentTypeForStats =
+ types::lookup<types::CONTENT_TYPE, short_enum_type_t>(contentType);
+ const auto encodingForStats = types::lookup<types::ENCODING, short_enum_type_t>(encoding);
+ const auto flagsForStats = types::lookup<types::OUTPUT_FLAG, short_enum_type_t>(flags);
+ const auto streamTypeForStats =
+ types::lookup<types::STREAM_TYPE, short_enum_type_t>(streamType);
+ const auto usageForStats = types::lookup<types::USAGE, short_enum_type_t>(usage);
+
+ LOG(DEBUG) << "key:" << key
+ << " id:" << id
+ << " outputDevices:" << outputDevices << "(" << outputDeviceBits
+ << ") deviceNames:" << deviceNames
+ << " deviceTimeNs:" << deviceTimeNs
+ << " encoding:" << encoding << "(" << encodingForStats
+ << ") frameCount:" << frameCount
+ << " intervalCount:" << intervalCount
+ << " sampleRate:" << sampleRate
+ << " underrun:" << underrun
+ << " flags:" << flags << "(" << flagsForStats
+ << ") callerName:" << callerName << "(" << callerNameForStats
+ << ") contentType:" << contentType << "(" << contentTypeForStats
+ << ") deviceLatencyMs:" << deviceLatencyMs
+ << " deviceStartupMs:" << deviceStartupMs
+ << " deviceVolume:" << deviceVolume
+ << " packageName:" << packageName
+ << " playbackPitch:" << playbackPitch
+ << " playbackSpeed:" << playbackSpeed
+ << " selectedDeviceId:" << selectedDeviceId
+ << " streamType:" << streamType << "(" << streamTypeForStats
+ << ") usage:" << usage << "(" << usageForStats
+ << ")";
#ifdef STATSD
if (mAudioAnalytics.mDeliverStatistics) {
(void)android::util::stats_write(
android::util::MEDIAMETRICS_AUDIOTRACKDEVICEUSAGE_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , devices.c_str()
+ , ENUM_EXTRACT(outputDeviceBits)
, deviceNames.c_str()
, deviceTimeNs
- , encoding.c_str()
+ , ENUM_EXTRACT(encodingForStats)
, frameCount
, intervalCount
, sampleRate
- , flags.c_str()
+ , ENUM_EXTRACT(flagsForStats)
, underrun
, packageName.c_str()
@@ -455,10 +529,10 @@
, (float)deviceStartupMs
, (float)deviceVolume
, selectedDeviceId
- , streamType.c_str()
- , usage.c_str()
- , contentType.c_str()
- , callerName.c_str()
+ , ENUM_EXTRACT(streamTypeForStats)
+ , ENUM_EXTRACT(usageForStats)
+ , ENUM_EXTRACT(contentTypeForStats)
+ , ENUM_EXTRACT(callerNameForStats)
);
}
#endif
@@ -490,7 +564,6 @@
item->get(AMEDIAMETRICS_PROP_NAME, &name);
ALOGD("(key=%s) a2dp connected device:%s atNs:%lld",
key.c_str(), name.c_str(), (long long)atNs);
-
}
void AudioAnalytics::DeviceConnection::createPatch(
@@ -502,27 +575,36 @@
item->get(AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH_A2DP") != std::string::npos) {
// TODO compare address
- int64_t timeDiff = item->getTimestamp();
+ int64_t timeDiffNs = item->getTimestamp();
if (mA2dpConnectionRequestNs == 0) {
ALOGD("%s: A2DP create patch didn't see a connection request", __func__);
- timeDiff -= mA2dpConnectionServiceNs;
+ timeDiffNs -= mA2dpConnectionServiceNs;
} else {
- timeDiff -= mA2dpConnectionRequestNs;
+ timeDiffNs -= mA2dpConnectionRequestNs;
}
- ALOGD("(key=%s) A2DP device connection time: %lld", key.c_str(), (long long)timeDiff);
+
mA2dpConnectionRequestNs = 0;
mA2dpConnectionServiceNs = 0;
++mA2dpConnectionSuccesses;
+ const auto connectionTimeMs = float(timeDiffNs * 1e-6);
+
+ const auto outputDeviceBits = types::lookup<types::OUTPUT_DEVICE, long_enum_type_t>(
+ "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP");
+
+ LOG(DEBUG) << "key:" << key
+ << " A2DP SUCCESS"
+ << " outputDevices:" << outputDeviceBits
+ << " connectionTimeMs:" << connectionTimeMs;
#ifdef STATSD
if (mAudioAnalytics.mDeliverStatistics) {
+ const long_enum_type_t inputDeviceBits{};
(void)android::util::stats_write(
android::util::MEDIAMETRICS_AUDIODEVICECONNECTION_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP"
- , android::util::MEDIAMETRICS_AUDIO_DEVICE_CONNECTION_REPORTED__RESULT__SUCCESS
- , /* connection_time_ms */ timeDiff * 1e-6 /* NS to MS */
+ , ENUM_EXTRACT(inputDeviceBits)
+ , ENUM_EXTRACT(outputDeviceBits)
+ , types::DEVICE_CONNECTION_RESULT_SUCCESS
+ , connectionTimeMs
, /* connection_count */ 1
);
}
@@ -552,18 +634,25 @@
void AudioAnalytics::DeviceConnection::expire() {
std::lock_guard l(mLock);
if (mA2dpConnectionRequestNs == 0) return; // ignore (this was an internal connection).
+
+#ifdef STATSD
+ const long_enum_type_t inputDeviceBits{};
+#endif
+ const auto outputDeviceBits = types::lookup<types::OUTPUT_DEVICE, long_enum_type_t>(
+ "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP");
+
if (mA2dpConnectionServiceNs == 0) {
- ALOGD("A2DP device connection service cancels");
++mA2dpConnectionJavaServiceCancels; // service did not connect to A2DP
+ LOG(DEBUG) << "A2DP CANCEL"
+ << " outputDevices:" << outputDeviceBits;
#ifdef STATSD
if (mAudioAnalytics.mDeliverStatistics) {
(void)android::util::stats_write(
android::util::MEDIAMETRICS_AUDIODEVICECONNECTION_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP"
- , android::util::MEDIAMETRICS_AUDIO_DEVICE_CONNECTION_REPORTED__RESULT__JAVA_SERVICE_CANCEL
+ , ENUM_EXTRACT(inputDeviceBits)
+ , ENUM_EXTRACT(outputDeviceBits)
+ , types::DEVICE_CONNECTION_RESULT_JAVA_SERVICE_CANCEL
, /* connection_time_ms */ 0.f
, /* connection_count */ 1
);
@@ -575,18 +664,19 @@
// AudioFlinger didn't play - an expiration may occur because there is no audio playing.
// Should we check elsewhere?
// TODO: disambiguate this case.
- ALOGD("A2DP device connection expired, state unknown");
mA2dpConnectionRequestNs = 0;
mA2dpConnectionServiceNs = 0;
++mA2dpConnectionUnknowns; // connection result unknown
+
+ LOG(DEBUG) << "A2DP UNKNOWN"
+ << " outputDevices:" << outputDeviceBits;
#ifdef STATSD
if (mAudioAnalytics.mDeliverStatistics) {
(void)android::util::stats_write(
android::util::MEDIAMETRICS_AUDIODEVICECONNECTION_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP"
- , android::util::MEDIAMETRICS_AUDIO_DEVICE_CONNECTION_REPORTED__RESULT__UNKNOWN
+ , ENUM_EXTRACT(inputDeviceBits)
+ , ENUM_EXTRACT(outputDeviceBits)
+ , types::DEVICE_CONNECTION_RESULT_UNKNOWN
, /* connection_time_ms */ 0.f
, /* connection_count */ 1
);
diff --git a/services/mediametrics/AudioAnalytics.h b/services/mediametrics/AudioAnalytics.h
index 9089d6f..138ddcc 100644
--- a/services/mediametrics/AudioAnalytics.h
+++ b/services/mediametrics/AudioAnalytics.h
@@ -109,7 +109,7 @@
*/
std::string getThreadFromTrack(const std::string& track) const;
- const bool mDeliverStatistics __unused = true;
+ const bool mDeliverStatistics;
// Actions is individually locked
AnalyticsActions mActions;
diff --git a/services/mediametrics/AudioPowerUsage.cpp b/services/mediametrics/AudioPowerUsage.cpp
index b1648d9..c441110 100644
--- a/services/mediametrics/AudioPowerUsage.cpp
+++ b/services/mediametrics/AudioPowerUsage.cpp
@@ -20,6 +20,7 @@
#include "AudioAnalytics.h"
#include "MediaMetricsService.h"
+#include "StringUtils.h"
#include <map>
#include <sstream>
#include <string>
@@ -117,7 +118,7 @@
int32_t AudioPowerUsage::deviceFromStringPairs(const std::string& device_strings) {
int32_t deviceMask = 0;
- const auto devaddrvec = MediaMetricsService::getDeviceAddressPairs(device_strings);
+ const auto devaddrvec = stringutils::getDeviceAddressPairs(device_strings);
for (const auto &[device, addr] : devaddrvec) {
int32_t combo_device = 0;
deviceFromString(device, combo_device);
diff --git a/services/mediametrics/AudioTypes.cpp b/services/mediametrics/AudioTypes.cpp
new file mode 100644
index 0000000..2a2dbaf
--- /dev/null
+++ b/services/mediametrics/AudioTypes.cpp
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include "AudioTypes.h"
+#include "StringUtils.h"
+#include <media/TypeConverter.h> // requires libmedia_helper to get the Audio code.
+
+namespace android::mediametrics::types {
+
+std::unordered_map<std::string, int64_t>& getAudioDeviceInMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones). This does NOT match audio_device_t.
+ static std::unordered_map<std::string, int64_t> map{
+ {"AUDIO_DEVICE_IN_COMMUNICATION", 1LL << 0},
+ {"AUDIO_DEVICE_IN_AMBIENT", 1LL << 1},
+ {"AUDIO_DEVICE_IN_BUILTIN_MIC", 1LL << 2},
+ {"AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET", 1LL << 3},
+ {"AUDIO_DEVICE_IN_WIRED_HEADSET", 1LL << 4},
+ {"AUDIO_DEVICE_IN_AUX_DIGITAL", 1LL << 5},
+ {"AUDIO_DEVICE_IN_HDMI", 1LL << 5}, // HDMI == AUX_DIGITAL (6 reserved)
+ {"AUDIO_DEVICE_IN_VOICE_CALL", 1LL << 7},
+ {"AUDIO_DEVICE_IN_TELEPHONY_RX", 1LL << 7}, // TELEPHONY_RX == VOICE_CALL (8 reserved)
+ {"AUDIO_DEVICE_IN_BACK_MIC", 1LL << 9},
+ {"AUDIO_DEVICE_IN_REMOTE_SUBMIX", 1LL << 10},
+ {"AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET", 1LL << 11},
+ {"AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET", 1LL << 12},
+ {"AUDIO_DEVICE_IN_USB_ACCESSORY", 1LL << 13},
+ {"AUDIO_DEVICE_IN_USB_DEVICE", 1LL << 14},
+ {"AUDIO_DEVICE_IN_FM_TUNER", 1LL << 15},
+ {"AUDIO_DEVICE_IN_TV_TUNER", 1LL << 16},
+ {"AUDIO_DEVICE_IN_LINE", 1LL << 17},
+ {"AUDIO_DEVICE_IN_SPDIF", 1LL << 18},
+ {"AUDIO_DEVICE_IN_BLUETOOTH_A2DP", 1LL << 19},
+ {"AUDIO_DEVICE_IN_LOOPBACK", 1LL << 20},
+ {"AUDIO_DEVICE_IN_IP", 1LL << 21},
+ {"AUDIO_DEVICE_IN_BUS", 1LL << 22},
+ {"AUDIO_DEVICE_IN_PROXY", 1LL << 23},
+ {"AUDIO_DEVICE_IN_USB_HEADSET", 1LL << 24},
+ {"AUDIO_DEVICE_IN_BLUETOOTH_BLE", 1LL << 25},
+ {"AUDIO_DEVICE_IN_HDMI_ARC", 1LL << 26},
+ {"AUDIO_DEVICE_IN_ECHO_REFERENCE", 1LL << 27},
+ {"AUDIO_DEVICE_IN_DEFAULT", 1LL << 28},
+ // R values above.
+ };
+ return map;
+}
+
+std::unordered_map<std::string, int64_t>& getAudioDeviceOutMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones). This does NOT match audio_device_t.
+ static std::unordered_map<std::string, int64_t> map{
+ {"AUDIO_DEVICE_OUT_EARPIECE", 1LL << 0},
+ {"AUDIO_DEVICE_OUT_SPEAKER", 1LL << 1},
+ {"AUDIO_DEVICE_OUT_WIRED_HEADSET", 1LL << 2},
+ {"AUDIO_DEVICE_OUT_WIRED_HEADPHONE", 1LL << 3},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_SCO", 1LL << 4},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET", 1LL << 5},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT", 1LL << 6},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_A2DP", 1LL << 7},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES", 1LL << 8},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER", 1LL << 9},
+ {"AUDIO_DEVICE_OUT_AUX_DIGITAL", 1LL << 10},
+ {"AUDIO_DEVICE_OUT_HDMI", 1LL << 10}, // HDMI == AUX_DIGITAL (11 reserved)
+ {"AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET", 1LL << 12},
+ {"AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET", 1LL << 13},
+ {"AUDIO_DEVICE_OUT_USB_ACCESSORY", 1LL << 14},
+ {"AUDIO_DEVICE_OUT_USB_DEVICE", 1LL << 15},
+ {"AUDIO_DEVICE_OUT_REMOTE_SUBMIX", 1LL << 16},
+ {"AUDIO_DEVICE_OUT_TELEPHONY_TX", 1LL << 17},
+ {"AUDIO_DEVICE_OUT_LINE", 1LL << 18},
+ {"AUDIO_DEVICE_OUT_HDMI_ARC", 1LL << 19},
+ {"AUDIO_DEVICE_OUT_SPDIF", 1LL << 20},
+ {"AUDIO_DEVICE_OUT_FM", 1LL << 21},
+ {"AUDIO_DEVICE_OUT_AUX_LINE", 1LL << 22},
+ {"AUDIO_DEVICE_OUT_SPEAKER_SAFE", 1LL << 23},
+ {"AUDIO_DEVICE_OUT_IP", 1LL << 24},
+ {"AUDIO_DEVICE_OUT_BUS", 1LL << 25},
+ {"AUDIO_DEVICE_OUT_PROXY", 1LL << 26},
+ {"AUDIO_DEVICE_OUT_USB_HEADSET", 1LL << 27},
+ {"AUDIO_DEVICE_OUT_HEARING_AID", 1LL << 28},
+ {"AUDIO_DEVICE_OUT_ECHO_CANCELLER", 1LL << 29},
+ {"AUDIO_DEVICE_OUT_DEFAULT", 1LL << 30},
+ // R values above.
+ };
+ return map;
+}
+
+std::unordered_map<std::string, int32_t>& getCallerNameMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones).
+ // This may be found in frameworks/av/media/libmediametrics/include/MediaMetricsConstants.h
+ static std::unordered_map<std::string, int32_t> map{
+ {"aaudio", 0}, // Native AAudio
+ {"java", 1}, // Java API layer
+ {"media", 2}, // libmedia (mediaplayer)
+ {"opensles", 3}, // Open SLES
+ {"rtp", 4}, // RTP communication
+ {"soundpool", 5}, // SoundPool
+ {"tonegenerator", 6}, // dial tones
+ {"unknown", 7}, // callerName not set
+ // R values above.
+ };
+ return map;
+}
+
+std::unordered_map<std::string, int32_t>& getThreadTypeMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones).
+ // This may be found in frameworks/av/services/audioflinger/Threads.h
+ static std::unordered_map<std::string, int32_t> map{
+ // UNKNOWN is -1
+ {"MIXER", 0}, // Thread class is MixerThread
+ {"DIRECT", 1}, // Thread class is DirectOutputThread
+ {"DUPLICATING", 2}, // Thread class is DuplicatingThread
+ {"RECORD", 3}, // Thread class is RecordThread
+ {"OFFLOAD", 4}, // Thread class is OffloadThread
+ {"MMAP_PLAYBACK", 5}, // Thread class for MMAP playback stream
+ {"MMAP_CAPTURE", 6}, // Thread class for MMAP capture stream
+ // R values above.
+ };
+ return map;
+}
+
+// Helper: Create the corresponding int32 from string flags split with '|'.
+template <typename Traits>
+int32_t int32FromFlags(const std::string &flags)
+{
+ const auto result = stringutils::split(flags, "|");
+ int32_t intFlags = 0;
+ for (const auto& flag : result) {
+ typename Traits::Type value;
+ if (!TypeConverter<Traits>::fromString(flag, value)) {
+ break;
+ }
+ intFlags |= value;
+ }
+ return intFlags;
+}
+
+template <typename Traits>
+std::string stringFromFlags(const std::string &flags, size_t len)
+{
+ const auto result = stringutils::split(flags, "|");
+ std::string sFlags;
+ for (const auto& flag : result) {
+ typename Traits::Type value;
+ if (!TypeConverter<Traits>::fromString(flag, value)) {
+ break;
+ }
+ if (len >= flag.size()) continue;
+ if (!sFlags.empty()) sFlags += "|";
+ sFlags += flag.c_str() + len;
+ }
+ return sFlags;
+}
+
+template <>
+int32_t lookup<CONTENT_TYPE>(const std::string &contentType)
+{
+ AudioContentTraits::Type value;
+ if (!TypeConverter<AudioContentTraits>::fromString(contentType, value)) {
+ value = AUDIO_CONTENT_TYPE_UNKNOWN;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<CONTENT_TYPE>(const std::string &contentType)
+{
+ AudioContentTraits::Type value;
+ if (!TypeConverter<AudioContentTraits>::fromString(contentType, value)) {
+ return "UNKNOWN";
+ }
+ return contentType.c_str() + sizeof("AUDIO_CONTENT_TYPE");
+}
+
+template <>
+int32_t lookup<ENCODING>(const std::string &encoding)
+{
+ FormatTraits::Type value;
+ if (!TypeConverter<FormatTraits>::fromString(encoding, value)) {
+ value = AUDIO_FORMAT_INVALID;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<ENCODING>(const std::string &encoding)
+{
+ FormatTraits::Type value;
+ if (!TypeConverter<FormatTraits>::fromString(encoding, value)) {
+ return "INVALID";
+ }
+ return encoding.c_str() + sizeof("AUDIO_FORMAT");
+}
+
+template <>
+int32_t lookup<INPUT_FLAG>(const std::string &inputFlag)
+{
+ return int32FromFlags<InputFlagTraits>(inputFlag);
+}
+
+template <>
+std::string lookup<INPUT_FLAG>(const std::string &inputFlag)
+{
+ return stringFromFlags<InputFlagTraits>(inputFlag, sizeof("AUDIO_INPUT_FLAG"));
+}
+
+template <>
+int32_t lookup<OUTPUT_FLAG>(const std::string &outputFlag)
+{
+ return int32FromFlags<OutputFlagTraits>(outputFlag);
+}
+
+template <>
+std::string lookup<OUTPUT_FLAG>(const std::string &outputFlag)
+{
+ return stringFromFlags<OutputFlagTraits>(outputFlag, sizeof("AUDIO_OUTPUT_FLAG"));
+}
+
+template <>
+int32_t lookup<SOURCE_TYPE>(const std::string &sourceType)
+{
+ SourceTraits::Type value;
+ if (!TypeConverter<SourceTraits>::fromString(sourceType, value)) {
+ value = AUDIO_SOURCE_DEFAULT;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<SOURCE_TYPE>(const std::string &sourceType)
+{
+ SourceTraits::Type value;
+ if (!TypeConverter<SourceTraits>::fromString(sourceType, value)) {
+ return "DEFAULT";
+ }
+ return sourceType.c_str() + sizeof("AUDIO_SOURCE");
+}
+
+template <>
+int32_t lookup<STREAM_TYPE>(const std::string &streamType)
+{
+ StreamTraits::Type value;
+ if (!TypeConverter<StreamTraits>::fromString(streamType, value)) {
+ value = AUDIO_STREAM_DEFAULT;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<STREAM_TYPE>(const std::string &streamType)
+{
+ StreamTraits::Type value;
+ if (!TypeConverter<StreamTraits>::fromString(streamType, value)) {
+ return "DEFAULT";
+ }
+ return streamType.c_str() + sizeof("AUDIO_STREAM");
+}
+
+template <>
+int32_t lookup<USAGE>(const std::string &usage)
+{
+ UsageTraits::Type value;
+ if (!TypeConverter<UsageTraits>::fromString(usage, value)) {
+ value = AUDIO_USAGE_UNKNOWN;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<USAGE>(const std::string &usage)
+{
+ UsageTraits::Type value;
+ if (!TypeConverter<UsageTraits>::fromString(usage, value)) {
+ return "UNKNOWN";
+ }
+ return usage.c_str() + sizeof("AUDIO_USAGE");
+}
+
+template <>
+int64_t lookup<INPUT_DEVICE>(const std::string &inputDevice)
+{
+ auto& map = getAudioDeviceInMap();
+ auto it = map.find(inputDevice);
+ if (it == map.end()) {
+ return 0;
+ }
+ return it->second;
+}
+
+template <>
+std::string lookup<INPUT_DEVICE>(const std::string &inputDevice)
+{
+ auto& map = getAudioDeviceInMap();
+ auto it = map.find(inputDevice);
+ if (it == map.end()) {
+ return "NONE";
+ }
+ return inputDevice.c_str() + sizeof("AUDIO_DEVICE_IN");
+}
+
+template <>
+int64_t lookup<OUTPUT_DEVICE>(const std::string &outputDevice)
+{
+ auto& map = getAudioDeviceOutMap();
+ auto it = map.find(outputDevice);
+ if (it == map.end()) {
+ return 0; // nothing
+ }
+ return it->second;
+}
+
+template <>
+std::string lookup<OUTPUT_DEVICE>(const std::string &outputDevice)
+{
+ auto& map = getAudioDeviceOutMap();
+ auto it = map.find(outputDevice);
+ if (it == map.end()) {
+ return "NONE";
+ }
+ return outputDevice.c_str() + sizeof("AUDIO_DEVICE_OUT");
+}
+
+template <>
+int32_t lookup<CALLER_NAME>(const std::string &callerName)
+{
+ auto& map = getCallerNameMap();
+ auto it = map.find(callerName);
+ if (it == map.end()) {
+ return 7; // return unknown
+ }
+ return it->second;
+}
+
+template <>
+std::string lookup<CALLER_NAME>(const std::string &callerName)
+{
+ auto& map = getCallerNameMap();
+ auto it = map.find(callerName);
+ if (it == map.end()) {
+ return "unknown";
+ }
+ return callerName;
+}
+
+template <>
+int32_t lookup<THREAD_TYPE>(const std::string &threadType)
+{
+ auto& map = getThreadTypeMap();
+ auto it = map.find(threadType);
+ if (it == map.end()) {
+ return -1; // note this as an illegal thread value as we don't have unknown here.
+ }
+ return it->second;
+}
+
+template <>
+std::string lookup<THREAD_TYPE>(const std::string &threadType)
+{
+ auto& map = getThreadTypeMap();
+ auto it = map.find(threadType);
+ if (it == map.end()) {
+ return "UNKNOWN";
+ }
+ return threadType;
+}
+
+bool isInputThreadType(const std::string &threadType)
+{
+ return threadType == "RECORD" || threadType == "MMAP_CAPTURE";
+}
+
+} // namespace android::mediametrics::types
diff --git a/services/mediametrics/AudioTypes.h b/services/mediametrics/AudioTypes.h
new file mode 100644
index 0000000..a094e6e
--- /dev/null
+++ b/services/mediametrics/AudioTypes.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 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 <string>
+#include <unordered_map>
+
+namespace android::mediametrics::types {
+
+// Helper methods that map mediametrics logged strings to
+// integer codes.
+std::unordered_map<std::string, int64_t>& getAudioDeviceInMap();
+std::unordered_map<std::string, int64_t>& getAudioDeviceOutMap();
+std::unordered_map<std::string, int32_t>& getCallerNameMap();
+std::unordered_map<std::string, int32_t>& getThreadTypeMap();
+
+// Enumeration for the device connection results.
+enum DeviceConnectionResult : int32_t {
+ DEVICE_CONNECTION_RESULT_UNKNOWN = 0, // Success is unknown.
+ DEVICE_CONNECTION_RESULT_SUCCESS = 1, // Audio delivered
+ DEVICE_CONNECTION_RESULT_JAVA_SERVICE_CANCEL = 2, // Canceled in Java service
+ // Do not modify the constants above after R. Adding new constants is fine.
+};
+
+// Enumeration for all the string translations to integers (generally int32_t) unless noted.
+enum AudioEnumCategory {
+ CALLER_NAME,
+ CONTENT_TYPE,
+ ENCODING,
+ INPUT_DEVICE, // int64_t
+ INPUT_FLAG,
+ OUTPUT_DEVICE, // int64_t
+ OUTPUT_FLAG,
+ SOURCE_TYPE,
+ STREAM_TYPE,
+ THREAD_TYPE,
+ USAGE,
+};
+
+// Convert a string (or arbitrary S) from an AudioEnumCategory to a particular type.
+// This is used to convert log std::strings back to the original type (int32_t or int64_t).
+//
+// For a string, generally there is a prefix "AUDIO_INPUT_FLAG" or some such that could
+// actually indicate the category so the AudioEnumCategory could be superfluous, but
+// we use it to find the proper default value in case of an unknown string.
+//
+// lookup<ENCODING, int32_t>("AUDIO_FORMAT_PCM_16_BIT") -> 1
+//
+template <AudioEnumCategory C, typename T, typename S>
+T lookup(const S &str);
+
+// Helper: Allow using a const char * in lieu of std::string.
+template <AudioEnumCategory C, typename T>
+T lookup(const char *str) {
+ return lookup<C, T, std::string>(str);
+}
+
+bool isInputThreadType(const std::string &threadType);
+
+} // namespace android::mediametrics::types
diff --git a/services/mediametrics/MediaMetricsService.cpp b/services/mediametrics/MediaMetricsService.cpp
index 584bd13..d682fed 100644
--- a/services/mediametrics/MediaMetricsService.cpp
+++ b/services/mediametrics/MediaMetricsService.cpp
@@ -95,58 +95,6 @@
}
}
-/* 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),
diff --git a/services/mediametrics/MediaMetricsService.h b/services/mediametrics/MediaMetricsService.h
index b8eb267..d152264 100644
--- a/services/mediametrics/MediaMetricsService.h
+++ b/services/mediametrics/MediaMetricsService.h
@@ -77,20 +77,6 @@
*/
static std::pair<std::string, int64_t> getSanitizedPackageNameAndVersionCode(uid_t uid);
- /**
- * Return string tokens from iterator, separated by spaces and reserved chars.
- */
- static std::string tokenizer(std::string::const_iterator& it,
- const std::string::const_iterator& end, const char *reserved);
-
- /**
- * Parse the devices string and return a vector of device address pairs.
- *
- * A failure to parse returns early with the contents that were able to be parsed.
- */
- static std::vector<std::pair<std::string, std::string>>
- getDeviceAddressPairs(const std::string &devices);
-
protected:
// Internal call where release is true if ownership of item is transferred
diff --git a/services/mediametrics/StringUtils.cpp b/services/mediametrics/StringUtils.cpp
new file mode 100644
index 0000000..50525bc
--- /dev/null
+++ b/services/mediametrics/StringUtils.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 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 "MediaMetricsService::stringutils"
+#include <utils/Log.h>
+
+#include "StringUtils.h"
+
+namespace android::mediametrics::stringutils {
+
+std::string 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};
+ }
+}
+
+std::vector<std::string> split(const std::string& flags, const char *delim)
+{
+ std::vector<std::string> result;
+ for (auto it = flags.begin(); ; ) {
+ auto flag = tokenizer(it, flags.end(), delim);
+ if (flag.empty() || !std::isalnum(flag[0])) return result;
+ result.emplace_back(std::move(flag));
+
+ // look for the delimeter and discard
+ auto token = tokenizer(it, flags.end(), delim);
+ if (token.size() != 1 || strchr(delim, token[0]) == nullptr) return result;
+ }
+}
+
+std::vector<std::pair<std::string, std::string>> 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
+ }
+}
+
+size_t replace(std::string &str, const char *targetChars, const char replaceChar)
+{
+ size_t replaced = 0;
+ for (char &c : str) {
+ if (strchr(targetChars, c) != nullptr) {
+ c = replaceChar;
+ ++replaced;
+ }
+ }
+ return replaced;
+}
+
+} // namespace android::mediametrics::stringutils
diff --git a/services/mediametrics/StringUtils.h b/services/mediametrics/StringUtils.h
new file mode 100644
index 0000000..d878720
--- /dev/null
+++ b/services/mediametrics/StringUtils.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 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 <string>
+#include <vector>
+
+namespace android::mediametrics::stringutils {
+
+/**
+ * Return string tokens from iterator, separated by spaces and reserved chars.
+ */
+std::string tokenizer(std::string::const_iterator& it,
+ const std::string::const_iterator& end, const char *reserved);
+
+/**
+ * Splits flags string based on delimeters (or, whitespace which is removed).
+ */
+std::vector<std::string> split(const std::string& flags, const char *delim);
+
+/**
+ * Parse the devices string and return a vector of device address pairs.
+ *
+ * A failure to parse returns early with the contents that were able to be parsed.
+ */
+std::vector<std::pair<std::string, std::string>> getDeviceAddressPairs(const std::string &devices);
+
+/**
+ * Replaces targetChars with replaceChar in string, returns number of chars replaced.
+ */
+size_t replace(std::string &str, const char *targetChars, const char replaceChar);
+
+} // namespace android::mediametrics::stringutils
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
index f7988f1..7da6306 100644
--- a/services/mediametrics/tests/mediametrics_tests.cpp
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -18,6 +18,7 @@
#include <utils/Log.h>
#include "MediaMetricsService.h"
+#include "StringUtils.h"
#include <stdio.h>
@@ -884,12 +885,12 @@
}
TEST(mediametrics_tests, device_parsing) {
- auto devaddr = android::MediaMetricsService::getDeviceAddressPairs("(DEVICE, )");
+ auto devaddr = android::mediametrics::stringutils::getDeviceAddressPairs("(DEVICE, )");
ASSERT_EQ((size_t)1, devaddr.size());
ASSERT_EQ("DEVICE", devaddr[0].first);
ASSERT_EQ("", devaddr[0].second);
- devaddr = android::MediaMetricsService::getDeviceAddressPairs(
+ devaddr = android::mediametrics::stringutils::getDeviceAddressPairs(
"(DEVICE1, A)|(D, ADDRB)");
ASSERT_EQ((size_t)2, devaddr.size());
ASSERT_EQ("DEVICE1", devaddr[0].first);
@@ -897,7 +898,7 @@
ASSERT_EQ("D", devaddr[1].first);
ASSERT_EQ("ADDRB", devaddr[1].second);
- devaddr = android::MediaMetricsService::getDeviceAddressPairs(
+ devaddr = android::mediametrics::stringutils::getDeviceAddressPairs(
"(A,B)|(C,D)");
ASSERT_EQ((size_t)2, devaddr.size());
ASSERT_EQ("A", devaddr[0].first);
@@ -905,7 +906,7 @@
ASSERT_EQ("C", devaddr[1].first);
ASSERT_EQ("D", devaddr[1].second);
- devaddr = android::MediaMetricsService::getDeviceAddressPairs(
+ devaddr = android::mediametrics::stringutils::getDeviceAddressPairs(
" ( A1 , B ) | ( C , D2 ) ");
ASSERT_EQ((size_t)2, devaddr.size());
ASSERT_EQ("A1", devaddr[0].first);