MediaMetrics: Add logSessionId handling
Test: adb shell dumpsys media.metrics
Test: atest mediametrics_tests
Bug: 181032765
Change-Id: I030555abeaf2f286b61a85032bc767581c2e605e
diff --git a/services/mediametrics/AudioAnalytics.cpp b/services/mediametrics/AudioAnalytics.cpp
index 3b2de76..9b7da0f 100644
--- a/services/mediametrics/AudioAnalytics.cpp
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -87,6 +87,7 @@
"selected_device_id",
"caller",
"source",
+ "log_session_id",
};
static constexpr const char * const AudioThreadDeviceUsageFields[] = {
@@ -124,6 +125,7 @@
"content_type",
"caller",
"traits",
+ "log_session_id",
};
static constexpr const char * const AudioDeviceConnectionFields[] = {
@@ -521,12 +523,18 @@
std::string source;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_SOURCE, &source);
+ // Android S
+ std::string logSessionId;
+ mAudioAnalytics.mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_LOGSESSIONID, &logSessionId);
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);
+ // Android S
+ const auto logSessionIdForStats = stringutils::sanitizeLogSessionId(logSessionId);
LOG(LOG_LEVEL) << "key:" << key
<< " id:" << id
@@ -541,7 +549,9 @@
<< ") packageName:" << packageName
<< " selectedDeviceId:" << selectedDeviceId
<< " callerName:" << callerName << "(" << callerNameForStats
- << ") source:" << source << "(" << sourceForStats << ")";
+ << ") source:" << source << "(" << sourceForStats
+ << ") logSessionId:" << logSessionId << "(" << logSessionIdForStats
+ << ")";
if (clientCalled // only log if client app called AudioRecord.
&& mAudioAnalytics.mDeliverStatistics) {
const auto [ result, str ] = sendToStatsd(AudioRecordDeviceUsageFields,
@@ -559,6 +569,7 @@
, selectedDeviceId
, ENUM_EXTRACT(callerNameForStats)
, ENUM_EXTRACT(sourceForStats)
+ , logSessionIdForStats.c_str()
);
ALOGV("%s: statsd %s", __func__, str.c_str());
mAudioAnalytics.mStatsdLog.log("%s", str.c_str());
@@ -659,6 +670,10 @@
std::string usage;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_USAGE, &usage);
+ // Android S
+ std::string logSessionId;
+ mAudioAnalytics.mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_LOGSESSIONID, &logSessionId);
const auto callerNameForStats =
types::lookup<types::CALLER_NAME, short_enum_type_t>(callerName);
@@ -671,6 +686,8 @@
const auto traitsForStats =
types::lookup<types::TRACK_TRAITS, short_enum_type_t>(traits);
const auto usageForStats = types::lookup<types::USAGE, short_enum_type_t>(usage);
+ // Android S
+ const auto logSessionIdForStats = stringutils::sanitizeLogSessionId(logSessionId);
LOG(LOG_LEVEL) << "key:" << key
<< " id:" << id
@@ -695,6 +712,7 @@
<< " streamType:" << streamType << "(" << streamTypeForStats
<< ") traits:" << traits << "(" << traitsForStats
<< ") usage:" << usage << "(" << usageForStats
+ << ") logSessionId:" << logSessionId << "(" << logSessionIdForStats
<< ")";
if (clientCalled // only log if client app called AudioTracks
&& mAudioAnalytics.mDeliverStatistics) {
@@ -719,6 +737,7 @@
, ENUM_EXTRACT(contentTypeForStats)
, ENUM_EXTRACT(callerNameForStats)
, ENUM_EXTRACT(traitsForStats)
+ , logSessionIdForStats.c_str()
);
ALOGV("%s: statsd %s", __func__, str.c_str());
mAudioAnalytics.mStatsdLog.log("%s", str.c_str());
diff --git a/services/mediametrics/StringUtils.h b/services/mediametrics/StringUtils.h
index 7a8bbee..37ed173 100644
--- a/services/mediametrics/StringUtils.h
+++ b/services/mediametrics/StringUtils.h
@@ -68,4 +68,82 @@
*/
size_t replace(std::string &str, const char *targetChars, const char replaceChar);
+// RFC 1421, 2045, 2152, 4648(4), 4880
+inline constexpr char Base64Table[] =
+ // 0000000000111111111122222222223333333333444444444455555555556666
+ // 0123456789012345678901234567890123456789012345678901234567890123
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+// RFC 4648(5) URL-safe Base64 encoding
+inline constexpr char Base64UrlTable[] =
+ // 0000000000111111111122222222223333333333444444444455555555556666
+ // 0123456789012345678901234567890123456789012345678901234567890123
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+// An constexpr struct that transposes/inverts a string conversion table.
+struct Transpose {
+ // constexpr bug, returning char still means -1 == 0xff, so we use unsigned char.
+ using base_char_t = unsigned char;
+ static inline constexpr base_char_t INVALID_CHAR = 0xff;
+
+ template <size_t N>
+ explicit constexpr Transpose(const char(&string)[N]) {
+ for (auto& e : mMap) {
+ e = INVALID_CHAR;
+ }
+ for (size_t i = 0; string[i] != 0; ++i) {
+ mMap[static_cast<size_t>(string[i]) & 0xff] = i;
+ }
+ }
+
+ constexpr base_char_t operator[] (size_t n) const {
+ return n < sizeof(mMap) ? mMap[n] : INVALID_CHAR;
+ }
+
+ constexpr const auto& get() const {
+ return mMap;
+ }
+
+private:
+ base_char_t mMap[256]; // construct an inverse character mapping.
+};
+
+// This table is used to convert an input char to a 6 bit (0 - 63) value.
+// If the input char is not in the Base64Url charset, Transpose::INVALID_CHAR is returned.
+inline constexpr Transpose InverseBase64UrlTable(Base64UrlTable);
+
+// Returns true if s consists of only valid Base64Url characters (no padding chars allowed).
+inline constexpr bool isBase64Url(const char *s) {
+ for (; *s != 0; ++s) {
+ if (InverseBase64UrlTable[(unsigned char)*s] == Transpose::INVALID_CHAR) return false;
+ }
+ return true;
+}
+
+// Returns true if s is a valid log session id: exactly 16 Base64Url characters.
+//
+// logSessionIds are a web-safe Base64Url RFC 4648(5) encoded string of 16 characters
+// (representing 96 unique bits 16 * 6).
+//
+// The string version is considered the reference representation. However, for ease of
+// manipulation and comparison, it may be converted to an int128.
+//
+// For int128 conversion, some common interpretations exist - for example
+// (1) the 16 Base64 chars can be converted 6 bits per char to a 96 bit value
+// (with the most significant 32 bits as zero) as there are only 12 unique bytes worth of data
+// or (2) the 16 Base64 chars can be used to directly fill the 128 bits of int128 assuming
+// the 16 chars are 16 bytes, filling the layout of the int128 variable.
+// Endianness of the data may follow whatever is convenient in the interpretation as long
+// as it is applied to each such conversion of string to int128 identically.
+//
+inline constexpr bool isLogSessionId(const char *s) {
+ return std::char_traits<std::decay_t<decltype(*s)>>::length(s) == 16 && isBase64Url(s);
+}
+
+// Returns either the original string or an empty string if isLogSessionId check fails.
+inline std::string sanitizeLogSessionId(const std::string& string) {
+ if (isLogSessionId(string.c_str())) return string;
+ return {}; // if not a logSessionId, return an empty string.
+}
+
} // namespace android::mediametrics::stringutils
diff --git a/services/mediametrics/statsd_audiorecord.cpp b/services/mediametrics/statsd_audiorecord.cpp
index 76f4b59..db809dc 100644
--- a/services/mediametrics/statsd_audiorecord.cpp
+++ b/services/mediametrics/statsd_audiorecord.cpp
@@ -32,6 +32,7 @@
#include <statslog.h>
#include "MediaMetricsService.h"
+#include "StringUtils.h"
#include "frameworks/proto_logging/stats/enums/stats/mediametrics/mediametrics.pb.h"
#include "iface_statsd.h"
@@ -134,19 +135,26 @@
metrics_proto.set_start_count(startcount);
}
-
std::string serialized;
if (!metrics_proto.SerializeToString(&serialized)) {
ALOGE("Failed to serialize audiorecord metrics");
return false;
}
+ // Android S
+ // log_session_id (string)
+ std::string logSessionId;
+ (void)item->getString("android.media.audiorecord.logSessionId", &logSessionId);
+ const auto logSessionIdForStats =
+ mediametrics::stringutils::sanitizeLogSessionId(logSessionId);
+
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);
+ bf_serialized,
+ logSessionIdForStats.c_str());
} else {
ALOGV("NOT sending: private data (len=%zu)", strlen(serialized.c_str()));
diff --git a/services/mediametrics/statsd_audiotrack.cpp b/services/mediametrics/statsd_audiotrack.cpp
index 6b08a78..fd809c8 100644
--- a/services/mediametrics/statsd_audiotrack.cpp
+++ b/services/mediametrics/statsd_audiotrack.cpp
@@ -32,6 +32,7 @@
#include <statslog.h>
#include "MediaMetricsService.h"
+#include "StringUtils.h"
#include "frameworks/proto_logging/stats/enums/stats/mediametrics/mediametrics.pb.h"
#include "iface_statsd.h"
@@ -132,12 +133,20 @@
return false;
}
+ // Android S
+ // log_session_id (string)
+ std::string logSessionId;
+ (void)item->getString("android.media.audiotrack.logSessionId", &logSessionId);
+ const auto logSessionIdForStats =
+ mediametrics::stringutils::sanitizeLogSessionId(logSessionId);
+
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);
+ bf_serialized,
+ logSessionIdForStats.c_str());
} else {
ALOGV("NOT sending: private data (len=%zu)", strlen(serialized.c_str()));
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
index 478355b..ac9c7fa 100644
--- a/services/mediametrics/tests/mediametrics_tests.cpp
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -1082,3 +1082,42 @@
//mediaMetrics->dump(fileno(stdout), {} /* args */);
}
#endif
+
+// Base64Url and isLogSessionId string utilities can be tested by static asserts.
+static_assert(mediametrics::stringutils::isBase64Url("abc"));
+static_assert(mediametrics::stringutils::InverseBase64UrlTable['A'] == 0);
+static_assert(mediametrics::stringutils::InverseBase64UrlTable['a'] == 26);
+static_assert(mediametrics::stringutils::InverseBase64UrlTable['!'] ==
+ mediametrics::stringutils::Transpose::INVALID_CHAR);
+static_assert(mediametrics::stringutils::InverseBase64UrlTable['@'] ==
+ mediametrics::stringutils::Transpose::INVALID_CHAR);
+static_assert(mediametrics::stringutils::InverseBase64UrlTable['#'] ==
+ mediametrics::stringutils::Transpose::INVALID_CHAR);
+static_assert(!mediametrics::stringutils::isBase64Url("!@#"));
+
+static_assert(mediametrics::stringutils::isLogSessionId("0123456789abcdef"));
+static_assert(!mediametrics::stringutils::isLogSessionId("abc"));
+static_assert(!mediametrics::stringutils::isLogSessionId("!@#"));
+static_assert(!mediametrics::stringutils::isLogSessionId("0123456789abcde!"));
+
+TEST(mediametrics_tests, sanitizeLogSessionId) {
+ // invalid id returns empty string.
+ ASSERT_EQ("", mediametrics::stringutils::sanitizeLogSessionId("abc"));
+
+ // valid id passes through.
+ std::string validId = "fedcba9876543210";
+ ASSERT_EQ(validId, mediametrics::stringutils::sanitizeLogSessionId(validId));
+
+ // one more char makes the id invalid
+ ASSERT_EQ("", mediametrics::stringutils::sanitizeLogSessionId(validId + "A"));
+
+ std::string validId2 = "ZYXWVUT123456789";
+ ASSERT_EQ(validId2, mediametrics::stringutils::sanitizeLogSessionId(validId2));
+
+ // one fewer char makes the id invalid
+ ASSERT_EQ("", mediametrics::stringutils::sanitizeLogSessionId(validId.c_str() + 1));
+
+ // replacing one character with an invalid character makes an invalid id.
+ validId2[3] = '!';
+ ASSERT_EQ("", mediametrics::stringutils::sanitizeLogSessionId(validId2));
+}