release-request-957cd691-fb71-4770-8ff7-a3b9602655a5-for-git_oc-mr1-release-4314464 snap-temp-L54400000099147910
Change-Id: I66781daede7cc1e01542dc9fe742fefae3ca085b
diff --git a/drm/mediadrm/plugins/clearkey/InitDataParser.cpp b/drm/mediadrm/plugins/clearkey/InitDataParser.cpp
index 6a4f8d5..caff393 100644
--- a/drm/mediadrm/plugins/clearkey/InitDataParser.cpp
+++ b/drm/mediadrm/plugins/clearkey/InitDataParser.cpp
@@ -136,7 +136,7 @@
AString encodedId;
for (size_t i = 0; i < keyIds.size(); ++i) {
encodedId.clear();
- android::encodeBase64(keyIds[i], kKeyIdSize, &encodedId);
+ android::encodeBase64Url(keyIds[i], kKeyIdSize, &encodedId);
if (i != 0) {
request.append(",");
}
diff --git a/drm/mediadrm/plugins/clearkey/tests/InitDataParserUnittest.cpp b/drm/mediadrm/plugins/clearkey/tests/InitDataParserUnittest.cpp
index 84ed242..8c49656 100644
--- a/drm/mediadrm/plugins/clearkey/tests/InitDataParserUnittest.cpp
+++ b/drm/mediadrm/plugins/clearkey/tests/InitDataParserUnittest.cpp
@@ -59,7 +59,7 @@
(size_t)requestString.find(kRequestSuffix));
for (size_t i = 0; i < expectedKeys.size(); ++i) {
AString encodedIdAString;
- android::encodeBase64(expectedKeys[i], kKeyIdSize,
+ android::encodeBase64Url(expectedKeys[i], kKeyIdSize,
&encodedIdAString);
String8 encodedId(encodedIdAString.c_str());
encodedId.removeAll(kBase64Padding);
@@ -231,5 +231,4 @@
attemptParseExpectingFailure(initData, kCencMimeType);
}
-
} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp b/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp
index c3b0d84..d9f3ea6 100644
--- a/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp
+++ b/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp
@@ -284,14 +284,14 @@
"\"keys\":"
"[{"
"\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\""
- "\"k\":\"SGVsbG8gRnJpZW5kISE\""
+ "\"k\":\"SGVsbG8gRnJpZW5kICE-Pw\""
"\"kty\":\"oct\""
"\"alg\":\"A128KW1\""
"}"
"{"
"\"kty\":\"oct\""
"\"alg\":\"A128KW2\""
- "\"k\":\"SGVsbG8gRnJpZW5kIQ\""
+ "\"k\":\"SGVsbG8gRnJpZW5kICE_\""
"\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\""
"}"
"{"
@@ -303,7 +303,7 @@
"{"
"\"alg\":\"A128KW3\""
"\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\""
- "\"k\":\"R29vZCBkYXkh\""
+ "\"k\":\"SGVsbG8gPz4-IEZyaWVuZCA_Pg\""
"\"kty\":\"oct\""
"}]"
"}");
@@ -313,8 +313,8 @@
EXPECT_TRUE(keys.size() == 3);
const String8 clearKeys[] =
- { String8("Hello Friend!!"), String8("Hello Friend!"),
- String8("Good day!") };
+ { String8("Hello Friend !>?"), String8("Hello Friend !?"),
+ String8("Hello ?>> Friend ?>") };
verifyKeys(keys, clearKeys);
}
diff --git a/media/libaaudio/examples/utils/AAudioExampleUtils.h b/media/libaaudio/examples/utils/AAudioExampleUtils.h
index 6cbcc58..9ef62c9 100644
--- a/media/libaaudio/examples/utils/AAudioExampleUtils.h
+++ b/media/libaaudio/examples/utils/AAudioExampleUtils.h
@@ -17,9 +17,14 @@
#ifndef AAUDIO_EXAMPLE_UTILS_H
#define AAUDIO_EXAMPLE_UTILS_H
-#include <unistd.h>
+#include <atomic>
+#include <linux/futex.h>
#include <sched.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
#include <aaudio/AAudio.h>
+#include <utils/Errors.h>
#define NANOS_PER_MICROSECOND ((int64_t)1000)
#define NANOS_PER_MILLISECOND (NANOS_PER_MICROSECOND * 1000)
@@ -40,6 +45,12 @@
return modeText;
}
+static void convertNanosecondsToTimespec(int64_t nanoseconds, struct timespec *time) {
+ time->tv_sec = nanoseconds / NANOS_PER_SECOND;
+ // Calculate the fractional nanoseconds. Avoids expensive % operation.
+ time->tv_nsec = nanoseconds - (time->tv_sec * NANOS_PER_SECOND);
+}
+
static int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC) {
struct timespec time;
int result = clock_gettime(clockId, &time);
@@ -79,4 +90,77 @@
return latencyMillis;
}
+// ================================================================================
+// These Futex calls are common online examples.
+static android::status_t sys_futex(void *addr1, int op, int val1,
+ struct timespec *timeout, void *addr2, int val3) {
+ android::status_t result = (android::status_t) syscall(SYS_futex, addr1,
+ op, val1, timeout,
+ addr2, val3);
+ return (result == 0) ? 0 : -errno;
+}
+
+static android::status_t futex_wake(void *addr, int numWake) {
+ // Use _PRIVATE because we are just using the futex in one process.
+ return sys_futex(addr, FUTEX_WAKE_PRIVATE, numWake, NULL, NULL, 0);
+}
+
+static android::status_t futex_wait(void *addr, int current, struct timespec *time) {
+ // Use _PRIVATE because we are just using the futex in one process.
+ return sys_futex(addr, FUTEX_WAIT_PRIVATE, current, time, NULL, 0);
+}
+
+// TODO better name?
+/**
+ * The WakeUp class is used to send a wakeup signal to one or more sleeping threads.
+ */
+class WakeUp {
+public:
+ WakeUp() : mValue(0) {}
+ explicit WakeUp(int32_t value) : mValue(value) {}
+
+ /**
+ * Wait until the internal value no longer matches the given value.
+ * Note that this code uses a futex, which is subject to spurious wake-ups.
+ * So check to make sure that the desired condition has been met.
+ *
+ * @return zero if the value changes or various negative errors including
+ * -ETIMEDOUT if a timeout occurs,
+ * or -EINTR if interrupted by a signal,
+ * or -EAGAIN or -EWOULDBLOCK if the internal value does not match the specified value
+ */
+ android::status_t wait(int32_t value, int64_t timeoutNanoseconds) {
+ struct timespec time;
+ convertNanosecondsToTimespec(timeoutNanoseconds, &time);
+ return futex_wait(&mValue, value, &time);
+ }
+
+ /**
+ * Increment value and wake up any threads that need to be woken.
+ *
+ * @return number of waiters woken up
+ */
+ android::status_t wake() {
+ ++mValue;
+ return futex_wake(&mValue, INT_MAX);
+ }
+
+ /**
+ * Set value and wake up any threads that need to be woken.
+ *
+ * @return number of waiters woken up
+ */
+ android::status_t wake(int32_t value) {
+ mValue.store(value);
+ return futex_wake(&mValue, INT_MAX);
+ }
+
+ int32_t get() {
+ return mValue.load();
+ }
+
+private:
+ std::atomic<int32_t> mValue;
+};
+
#endif // AAUDIO_EXAMPLE_UTILS_H
diff --git a/media/libaaudio/examples/utils/AAudioSimplePlayer.h b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
index cc0cb34..d2e7f23 100644
--- a/media/libaaudio/examples/utils/AAudioSimplePlayer.h
+++ b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
@@ -23,6 +23,7 @@
#include <sched.h>
#include <aaudio/AAudio.h>
+#include <atomic>
#include "AAudioArgsParser.h"
#include "SineGenerator.h"
@@ -219,18 +220,26 @@
AAudioStream *mStream = nullptr;
aaudio_sharing_mode_t mRequestedSharingMode = SHARING_MODE;
aaudio_performance_mode_t mRequestedPerformanceMode = PERFORMANCE_MODE;
+
};
typedef struct SineThreadedData_s {
+
SineGenerator sineOsc1;
SineGenerator sineOsc2;
int64_t framesTotal = 0;
int64_t nextFrameToGlitch = FORCED_UNDERRUN_PERIOD_FRAMES;
int32_t minNumFrames = INT32_MAX;
int32_t maxNumFrames = 0;
- int scheduler;
+
+ int scheduler = 0;
bool schedulerChecked = false;
bool forceUnderruns = false;
+
+ AAudioSimplePlayer simplePlayer;
+ int32_t callbackCount = 0;
+ WakeUp waker{AAUDIO_OK};
+
} SineThreadedData_t;
// Callback function that fills the audio output buffer.
@@ -247,6 +256,7 @@
return AAUDIO_CALLBACK_RESULT_STOP;
}
SineThreadedData_t *sineData = (SineThreadedData_t *) userData;
+ sineData->callbackCount++;
sineData->framesTotal += numFrames;
@@ -304,9 +314,16 @@
void SimplePlayerErrorCallbackProc(
AAudioStream *stream __unused,
void *userData __unused,
- aaudio_result_t error)
-{
- printf("Error Callback, error: %d\n",(int)error);
+ aaudio_result_t error) {
+ // should not happen but just in case...
+ if (userData == nullptr) {
+ printf("ERROR - MyPlayerErrorCallbackProc needs userData\n");
+ return;
+ }
+ SineThreadedData_t *sineData = (SineThreadedData_t *) userData;
+ android::status_t ret = sineData->waker.wake(error);
+ printf("Error Callback, error: %d, futex wake returns %d\n", error, ret);
}
+
#endif //AAUDIO_SIMPLE_PLAYER_H
diff --git a/media/libaaudio/examples/utils/SineGenerator.h b/media/libaaudio/examples/utils/SineGenerator.h
index 64b772d..a755582 100644
--- a/media/libaaudio/examples/utils/SineGenerator.h
+++ b/media/libaaudio/examples/utils/SineGenerator.h
@@ -58,6 +58,13 @@
}
}
+ void setAmplitude(double amplitude) {
+ mAmplitude = amplitude;
+ }
+ double getAmplitude() const {
+ return mAmplitude;
+ }
+
private:
void advancePhase() {
mPhase += mPhaseIncrement;
diff --git a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
index 071ca87..2280b72 100644
--- a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
+++ b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
@@ -15,6 +15,7 @@
*/
// Play sine waves using an AAudio callback.
+// If a disconnection occurs then reopen the stream on the new device.
#include <assert.h>
#include <unistd.h>
@@ -22,33 +23,32 @@
#include <sched.h>
#include <stdio.h>
#include <math.h>
+#include <string.h>
#include <time.h>
#include <aaudio/AAudio.h>
#include "AAudioExampleUtils.h"
#include "AAudioSimplePlayer.h"
#include "../../utils/AAudioSimplePlayer.h"
-int main(int argc, const char **argv)
+/**
+ * Open stream, play some sine waves, then close the stream.
+ *
+ * @param argParser
+ * @return AAUDIO_OK or negative error code
+ */
+static aaudio_result_t testOpenPlayClose(AAudioArgsParser &argParser)
{
- AAudioArgsParser argParser;
- AAudioSimplePlayer player;
SineThreadedData_t myData;
- aaudio_result_t result;
- int32_t actualSampleRate;
+ AAudioSimplePlayer &player = myData.simplePlayer;
+ aaudio_result_t result = AAUDIO_OK;
+ bool disconnected = false;
+ int64_t startedAtNanos;
- // Make printf print immediately so that debug info is not stuck
- // in a buffer if we hang or crash.
- setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
-
- printf("%s - Play a sine sweep using an AAudio callback V0.1.3\n", argv[0]);
-
+ printf("----------------------- run complete test --------------------------\n");
myData.schedulerChecked = false;
+ myData.callbackCount = 0;
myData.forceUnderruns = false; // set true to test AAudioStream_getXRunCount()
- if (argParser.parseArgs(argc, argv)) {
- return EXIT_FAILURE;
- }
-
result = player.open(argParser,
SimplePlayerDataCallbackProc, SimplePlayerErrorCallbackProc, &myData);
if (result != AAUDIO_OK) {
@@ -58,13 +58,19 @@
argParser.compareWithStream(player.getStream());
- actualSampleRate = player.getSampleRate();
- myData.sineOsc1.setup(440.0, actualSampleRate);
- myData.sineOsc1.setSweep(300.0, 600.0, 5.0);
- myData.sineOsc2.setup(660.0, actualSampleRate);
- myData.sineOsc2.setSweep(350.0, 900.0, 7.0);
+ // Setup sine wave generators.
+ {
+ int32_t actualSampleRate = player.getSampleRate();
+ myData.sineOsc1.setup(440.0, actualSampleRate);
+ myData.sineOsc1.setSweep(300.0, 600.0, 5.0);
+ myData.sineOsc1.setAmplitude(0.2);
+ myData.sineOsc2.setup(660.0, actualSampleRate);
+ myData.sineOsc2.setSweep(350.0, 900.0, 7.0);
+ myData.sineOsc2.setAmplitude(0.2);
+ }
#if 0
+ // writes not allowed for callback streams
result = player.prime(); // FIXME crashes AudioTrack.cpp
if (result != AAUDIO_OK) {
fprintf(stderr, "ERROR - player.prime() returned %d\n", result);
@@ -78,34 +84,32 @@
goto error;
}
+ // Play a sine wave in the background.
printf("Sleep for %d seconds while audio plays in a callback thread.\n",
argParser.getDurationSeconds());
+ startedAtNanos = getNanoseconds(CLOCK_MONOTONIC);
for (int second = 0; second < argParser.getDurationSeconds(); second++)
{
- const struct timespec request = { .tv_sec = 1, .tv_nsec = 0 };
- (void) clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, &request, NULL /*remain*/);
-
- aaudio_stream_state_t state;
- result = AAudioStream_waitForStateChange(player.getStream(),
- AAUDIO_STREAM_STATE_CLOSED,
- &state,
- 0);
+ // Sleep a while. Wake up early if there is an error, for example a DISCONNECT.
+ long ret = myData.waker.wait(AAUDIO_OK, NANOS_PER_SECOND);
+ int64_t millis = (getNanoseconds(CLOCK_MONOTONIC) - startedAtNanos) / NANOS_PER_MILLISECOND;
+ result = myData.waker.get();
+ printf("wait() returns %ld, aaudio_result = %d, at %6d millis"
+ ", second = %d, framesWritten = %8d, underruns = %d\n",
+ ret, result, (int) millis,
+ second,
+ (int) AAudioStream_getFramesWritten(player.getStream()),
+ (int) AAudioStream_getXRunCount(player.getStream()));
if (result != AAUDIO_OK) {
- fprintf(stderr, "ERROR - AAudioStream_waitForStateChange() returned %d\n", result);
- goto error;
- }
- if (state != AAUDIO_STREAM_STATE_STARTING && state != AAUDIO_STREAM_STATE_STARTED) {
- printf("Stream state is %d %s!\n", state, AAudio_convertStreamStateToText(state));
+ if (result == AAUDIO_ERROR_DISCONNECTED) {
+ disconnected = true;
+ }
break;
}
- printf("framesWritten = %d, underruns = %d\n",
- (int) AAudioStream_getFramesWritten(player.getStream()),
- (int) AAudioStream_getXRunCount(player.getStream())
- );
}
- printf("Woke up now.\n");
+ printf("AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
- printf("call stop()\n");
+ printf("call stop() callback # = %d\n", myData.callbackCount);
result = player.stop();
if (result != AAUDIO_OK) {
goto error;
@@ -126,10 +130,28 @@
printf("max numFrames = %8d\n", (int) myData.maxNumFrames);
printf("SUCCESS\n");
- return EXIT_SUCCESS;
error:
player.close();
- printf("exiting - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
- return EXIT_FAILURE;
+ return disconnected ? AAUDIO_ERROR_DISCONNECTED : result;
}
+int main(int argc, const char **argv)
+{
+ AAudioArgsParser argParser;
+ aaudio_result_t result;
+
+ // Make printf print immediately so that debug info is not stuck
+ // in a buffer if we hang or crash.
+ setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
+
+ printf("%s - Play a sine sweep using an AAudio callback V0.1.2\n", argv[0]);
+
+ if (argParser.parseArgs(argc, argv)) {
+ return EXIT_FAILURE;
+ }
+
+ // Keep looping until we can complete the test without disconnecting.
+ while((result = testOpenPlayClose(argParser)) == AAUDIO_ERROR_DISCONNECTED);
+
+ return (result) ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/media/libaudioclient/AudioEffect.cpp b/media/libaudioclient/AudioEffect.cpp
index a5f9ab6..b1cb0e7 100644
--- a/media/libaudioclient/AudioEffect.cpp
+++ b/media/libaudioclient/AudioEffect.cpp
@@ -135,7 +135,11 @@
&mStatus, &mId, &enabled);
if (iEffect == 0 || (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS)) {
- ALOGE("set(): AudioFlinger could not create effect, status: %d", mStatus);
+ char typeBuffer[64], uuidBuffer[64];
+ guidToString(type, typeBuffer, sizeof(typeBuffer));
+ guidToString(uuid, uuidBuffer, sizeof(uuidBuffer));
+ ALOGE("set(): AudioFlinger could not create effect %s / %s, status: %d",
+ typeBuffer, uuidBuffer, mStatus);
if (iEffect == 0) {
mStatus = NO_INIT;
}
diff --git a/media/libmediametrics/MediaAnalyticsItem.cpp b/media/libmediametrics/MediaAnalyticsItem.cpp
index 43881b3..47a147e 100644
--- a/media/libmediametrics/MediaAnalyticsItem.cpp
+++ b/media/libmediametrics/MediaAnalyticsItem.cpp
@@ -120,6 +120,8 @@
// key as part of constructor
dst->mPid = this->mPid;
dst->mUid = this->mUid;
+ dst->mPkgName = this->mPkgName;
+ dst->mPkgVersionCode = this->mPkgVersionCode;
dst->mSessionID = this->mSessionID;
dst->mTimestamp = this->mTimestamp;
dst->mFinalized = this->mFinalized;
@@ -201,6 +203,24 @@
return mUid;
}
+MediaAnalyticsItem &MediaAnalyticsItem::setPkgName(AString pkgName) {
+ mPkgName = pkgName;
+ return *this;
+}
+
+AString MediaAnalyticsItem::getPkgName() const {
+ return mPkgName;
+}
+
+MediaAnalyticsItem &MediaAnalyticsItem::setPkgVersionCode(int32_t pkgVersionCode) {
+ mPkgVersionCode = pkgVersionCode;
+ return *this;
+}
+
+int32_t MediaAnalyticsItem::getPkgVersionCode() const {
+ return mPkgVersionCode;
+}
+
// this key is for the overall record -- "codec", "player", "drm", etc
MediaAnalyticsItem &MediaAnalyticsItem::setKey(MediaAnalyticsItem::Key key) {
mKey = key;
@@ -263,11 +283,29 @@
i = mPropCount++;
prop = &mProps[i];
prop->setName(name, len);
+ prop->mType = kTypeNone; // make caller set type info
}
return prop;
}
+// used within the summarizers; return whether property existed
+bool MediaAnalyticsItem::removeProp(const char *name) {
+ size_t len = strlen(name);
+ size_t i = findPropIndex(name, len);
+ if (i < mPropCount) {
+ Prop *prop = &mProps[i];
+ clearProp(prop);
+ if (i != mPropCount-1) {
+ // in the middle, bring last one down to fill gap
+ mProps[i] = mProps[mPropCount-1];
+ }
+ mPropCount--;
+ return true;
+ }
+ return false;
+}
+
// set the values
void MediaAnalyticsItem::setInt32(MediaAnalyticsItem::Attr name, int32_t value) {
Prop *prop = allocateProp(name);
@@ -568,6 +606,10 @@
// into 'this' object
// .. we make a copy of the string to put away.
mKey = data.readCString();
+ mPid = data.readInt32();
+ mUid = data.readInt32();
+ mPkgName = data.readCString();
+ mPkgVersionCode = data.readInt32();
mSessionID = data.readInt64();
mFinalized = data.readInt32();
mTimestamp = data.readInt64();
@@ -611,6 +653,10 @@
data->writeCString(mKey.c_str());
+ data->writeInt32(mPid);
+ data->writeInt32(mUid);
+ data->writeCString(mPkgName.c_str());
+ data->writeInt32(mPkgVersionCode);
data->writeInt64(mSessionID);
data->writeInt32(mFinalized);
data->writeInt64(mTimestamp);
@@ -651,21 +697,54 @@
AString MediaAnalyticsItem::toString() {
+ return toString(-1);
+}
- AString result = "(";
+AString MediaAnalyticsItem::toString(int version) {
+
+ // v0 : released with 'o'
+ // v1 : bug fix (missing pid/finalized separator),
+ // adds apk name, apk version code
+
+ if (version <= PROTO_FIRST) {
+ // default to original v0 format, until proper parsers are in place
+ version = PROTO_V0;
+ } else if (version > PROTO_LAST) {
+ version = PROTO_LAST;
+ }
+
+ AString result;
char buffer[512];
+ if (version == PROTO_V0) {
+ result = "(";
+ } else {
+ snprintf(buffer, sizeof(buffer), "[%d:", version);
+ result.append(buffer);
+ }
+
// same order as we spill into the parcel, although not required
// key+session are our primary matching criteria
- //RBE ALOGD("mKey.c_str");
result.append(mKey.c_str());
- //RBE ALOGD("post-mKey.c_str");
result.append(":");
snprintf(buffer, sizeof(buffer), "%" PRId64 ":", mSessionID);
result.append(buffer);
- // we need these internally, but don't want to upload them
- snprintf(buffer, sizeof(buffer), "%d:%d", mUid, mPid);
+ snprintf(buffer, sizeof(buffer), "%d:", mUid);
+ result.append(buffer);
+
+ if (version >= PROTO_V1) {
+ result.append(mPkgName);
+ snprintf(buffer, sizeof(buffer), ":%d:", mPkgVersionCode);
+ result.append(buffer);
+ }
+
+ // in 'o' (v1) , the separator between pid and finalized was omitted
+ if (version <= PROTO_V0) {
+ snprintf(buffer, sizeof(buffer), "%d", mPid);
+ } else {
+ snprintf(buffer, sizeof(buffer), "%d:", mPid);
+ }
result.append(buffer);
snprintf(buffer, sizeof(buffer), "%d:", mFinalized);
@@ -713,7 +792,11 @@
result.append(buffer);
}
- result.append(")");
+ if (version == PROTO_V0) {
+ result.append(")");
+ } else {
+ result.append("]");
+ }
return result;
}
diff --git a/media/libmediametrics/include/MediaAnalyticsItem.h b/media/libmediametrics/include/MediaAnalyticsItem.h
index dc501b2..41b9658 100644
--- a/media/libmediametrics/include/MediaAnalyticsItem.h
+++ b/media/libmediametrics/include/MediaAnalyticsItem.h
@@ -75,6 +75,14 @@
typedef const char *Attr;
+ enum {
+ PROTO_V0 = 0,
+ PROTO_FIRST = PROTO_V0,
+ PROTO_V1 = 1,
+ PROTO_LAST = PROTO_V1,
+ };
+
+
public:
// access functions for the class
@@ -161,11 +169,18 @@
MediaAnalyticsItem &setUid(uid_t);
uid_t getUid() const;
+ MediaAnalyticsItem &setPkgName(AString);
+ AString getPkgName() const;
+
+ MediaAnalyticsItem &setPkgVersionCode(int32_t);
+ int32_t getPkgVersionCode() const;
+
// our serialization code for binder calls
int32_t writeToParcel(Parcel *);
int32_t readFromParcel(const Parcel&);
AString toString();
+ AString toString(int version);
// are we collecting analytics data
static bool isEnabled();
@@ -188,6 +203,8 @@
// to help validate that A doesn't mess with B's records
pid_t mPid;
uid_t mUid;
+ AString mPkgName;
+ int32_t mPkgVersionCode;
// let's reuse a binder connection
static sp<IMediaAnalyticsService> sAnalyticsService;
@@ -228,6 +245,7 @@
size_t findPropIndex(const char *name, size_t len);
Prop *findProp(const char *name);
Prop *allocateProp(const char *name);
+ bool removeProp(const char *name);
size_t mPropCount;
size_t mPropSize;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index 0fc1aa7..dc29761 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -69,6 +69,7 @@
mPlayer(new NuPlayer(pid)),
mPlayerFlags(0),
mAnalyticsItem(NULL),
+ mClientUid(-1),
mAtEOS(false),
mLooping(false),
mAutoLoop(false) {
@@ -109,6 +110,10 @@
status_t NuPlayerDriver::setUID(uid_t uid) {
mPlayer->setUID(uid);
+ mClientUid = uid;
+ if (mAnalyticsItem) {
+ mAnalyticsItem->setUid(mClientUid);
+ }
return OK;
}
@@ -601,6 +606,7 @@
mAnalyticsItem = new MediaAnalyticsItem("nuplayer");
if (mAnalyticsItem) {
mAnalyticsItem->generateSessionID();
+ mAnalyticsItem->setUid(mClientUid);
}
} else {
ALOGV("did not have anything to record");
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
index c5ddcb0..d0cf1dd 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -132,11 +132,13 @@
uint32_t mPlayerFlags;
MediaAnalyticsItem *mAnalyticsItem;
+ uid_t mClientUid;
bool mAtEOS;
bool mLooping;
bool mAutoLoop;
+
void updateMetrics(const char *where);
void logMetrics(const char *where);
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 0e60b2e..ec48561 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -4322,9 +4322,14 @@
h264type.bUseHadamard = OMX_TRUE;
h264type.nRefFrames = 2;
h264type.nBFrames = mLatency == 0 ? 1 : std::min(1U, mLatency - 1);
+
+ // disable B-frames until MPEG4Writer can guarantee finalizing files with B-frames
+ h264type.nRefFrames = 1;
+ h264type.nBFrames = 0;
+
h264type.nPFrames = setPFramesSpacing(iFrameInterval, frameRate, h264type.nBFrames);
h264type.nAllowedPictureTypes =
- OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP | OMX_VIDEO_PictureTypeB;
+ OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP;
h264type.nRefIdx10ActiveMinus1 = 0;
h264type.nRefIdx11ActiveMinus1 = 0;
h264type.bEntropyCodingCABAC = OMX_TRUE;
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 5ef0f56..6f59fac 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -5224,6 +5224,7 @@
void MPEG4Extractor::populateMetrics() {
ALOGV("MPEG4Extractor::populateMetrics");
+ // write into mAnalyticsItem
}
static bool LegacySniffMPEG4(
diff --git a/media/libstagefright/foundation/base64.cpp b/media/libstagefright/foundation/base64.cpp
index cc89064..8f32582 100644
--- a/media/libstagefright/foundation/base64.cpp
+++ b/media/libstagefright/foundation/base64.cpp
@@ -23,6 +23,7 @@
sp<ABuffer> decodeBase64(const AString &s) {
size_t n = s.size();
+
if ((n % 4) != 0) {
return NULL;
}
@@ -45,7 +46,6 @@
size_t outLen = (n / 4) * 3 - padding;
sp<ABuffer> buffer = new ABuffer(outLen);
-
uint8_t *out = buffer->data();
if (out == NULL || buffer->size() < outLen) {
return NULL;
@@ -61,9 +61,9 @@
value = 26 + c - 'a';
} else if (c >= '0' && c <= '9') {
value = 52 + c - '0';
- } else if (c == '+') {
+ } else if (c == '+' || c == '-') {
value = 62;
- } else if (c == '/') {
+ } else if (c == '/' || c == '_') {
value = 63;
} else if (c != '=') {
return NULL;
@@ -144,4 +144,26 @@
}
}
+void encodeBase64Url(
+ const void *_data, size_t size, AString *out) {
+ encodeBase64(_data, size, out);
+
+ if ((-1 != out->find("+")) || (-1 != out->find("/"))) {
+ size_t outLen = out->size();
+ char *base64url = new char[outLen];
+ for (size_t i = 0; i < outLen; ++i) {
+ if (out->c_str()[i] == '+')
+ base64url[i] = '-';
+ else if (out->c_str()[i] == '/')
+ base64url[i] = '_';
+ else
+ base64url[i] = out->c_str()[i];
+ }
+
+ out->setTo(base64url, outLen);
+ delete[] base64url;
+ }
+}
+
+
} // namespace android
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/base64.h b/media/libstagefright/foundation/include/media/stagefright/foundation/base64.h
index e340b89..abc95e0 100644
--- a/media/libstagefright/foundation/include/media/stagefright/foundation/base64.h
+++ b/media/libstagefright/foundation/include/media/stagefright/foundation/base64.h
@@ -28,6 +28,8 @@
sp<ABuffer> decodeBase64(const AString &s);
void encodeBase64(const void *data, size_t size, AString *out);
+void encodeBase64Url(const void *data, size_t size, AString *out);
+
} // namespace android
#endif // BASE_64_H_
diff --git a/media/libstagefright/foundation/tests/Android.mk b/media/libstagefright/foundation/tests/Android.mk
index d741c6f..a9e3c76 100644
--- a/media/libstagefright/foundation/tests/Android.mk
+++ b/media/libstagefright/foundation/tests/Android.mk
@@ -9,11 +9,13 @@
LOCAL_SRC_FILES := \
AData_test.cpp \
+ Base64_test.cpp \
Flagged_test.cpp \
TypeTraits_test.cpp \
Utils_test.cpp \
LOCAL_SHARED_LIBRARIES := \
+ liblog \
libstagefright_foundation \
libutils \
diff --git a/media/libstagefright/foundation/tests/Base64_test.cpp b/media/libstagefright/foundation/tests/Base64_test.cpp
new file mode 100644
index 0000000..7a4289e
--- /dev/null
+++ b/media/libstagefright/foundation/tests/Base64_test.cpp
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+#include <utils/Log.h>
+
+#include "gtest/gtest.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/AStringUtils.h>
+#include <media/stagefright/foundation/base64.h>
+
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+namespace {
+const android::String8 kBase64Padding("=");
+};
+
+namespace android {
+
+class Base64Test : public ::testing::Test {
+};
+
+void verifyDecode(const AString* expected, const AString* in) {
+ size_t numTests = 0;
+ while (!expected[numTests].empty())
+ ++numTests;
+
+ for (size_t i = 0; i < numTests; ++i) {
+ // Since android::decodeBase64() requires padding characters,
+ // add them so length of encoded text is exactly a multiple of 4.
+ int remainder = in[i].size() % 4;
+ String8 paddedText(in[i].c_str());
+ if (remainder > 0) {
+ for (int i = 0; i < 4 - remainder; ++i) {
+ paddedText.append(kBase64Padding);
+ }
+ }
+ sp<ABuffer> result = decodeBase64(AString(paddedText.string()));
+
+ ASSERT_EQ(AStringUtils::Compare(expected[i].c_str(),
+ reinterpret_cast<char*>(result->data()),
+ expected[i].size(), false), 0);
+ }
+}
+
+void verifyEncode(const AString* expected, const AString* in) {
+ size_t numTests = 0;
+ while (!expected[numTests].empty())
+ ++numTests;
+
+ AString out = AString("");
+ for (size_t i = 0; i < numTests; ++i) {
+ encodeBase64Url(in[i].c_str(), in[i].size(), &out);
+
+ ASSERT_EQ(AStringUtils::Compare(expected[i].c_str(), out.c_str(),
+ expected[i].size(), false), 0);
+ }
+}
+
+TEST_F(Base64Test, TestDecodeBase64) {
+ const AString base64[] = {
+ AString("SGVsbG8gRnJpZW5kIQ"),
+ AString("R29vZCBkYXkh"),
+ AString("") // string to signal end of array
+ };
+
+ const AString clearText[] = {
+ AString("Hello Friend!"),
+ AString("Good day!"),
+ AString("")
+ };
+
+ verifyDecode(clearText, base64);
+}
+
+TEST_F(Base64Test, TestDecodeBase64Url) {
+ const AString base64Url[] = {
+ AString("SGVsbG8gRnJpZW5kICE-Pw"),
+ AString("SGVsbG8gRnJpZW5kICE_"),
+ AString("SGVsbG8gPz4-IEZyaWVuZCA_Pg"),
+ AString("")
+ };
+
+ const AString clearText[] = {
+ AString("Hello Friend !>?"),
+ AString("Hello Friend !?"),
+ AString("Hello ?>> Friend ?>"),
+ AString("")
+ };
+
+ verifyDecode(clearText, base64Url);
+}
+
+TEST_F(Base64Test, TestDecodeMalformedBase64) {
+ const AString base64Url[] = {
+ AString("1?GawgguFyGrWKav7AX4VKUg"), // fail on parsing
+ AString("GawgguFyGrWKav7AX4V???"), // fail on length not multiple of 4
+ AString("GawgguFyGrWKav7AX4VKUg"), // ditto
+ };
+
+ for (size_t i = 0; i < 3; ++i) {
+ sp<ABuffer> result = decodeBase64(AString(base64Url[i]));
+ EXPECT_TRUE(result == nullptr);
+ }
+}
+
+TEST_F(Base64Test, TestEncodeBase64) {
+ const AString clearText[] = {
+ AString("Hello Friend!"),
+ AString("Good day!"),
+ AString("")
+ };
+
+ const AString base64[] = {
+ AString("SGVsbG8gRnJpZW5kIQ=="),
+ AString("R29vZCBkYXkh"),
+ AString("")
+ };
+
+ verifyEncode(base64, clearText);
+}
+
+TEST_F(Base64Test, TestEncodeBase64Url) {
+ const AString clearText[] = {
+ AString("Hello Friend !>?"),
+ AString("Hello Friend !?"),
+ AString("Hello ?>> Friend ?>"),
+ AString("")
+ };
+
+ const AString base64Url[] = {
+ AString("SGVsbG8gRnJpZW5kICE-Pw=="),
+ AString("SGVsbG8gRnJpZW5kICE_"),
+ AString("SGVsbG8gPz4-IEZyaWVuZCA_Pg"),
+ AString("")
+ };
+
+ verifyEncode(base64Url, clearText);
+}
+
+} // namespace android
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index a15b256..13522f5 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -2603,7 +2603,7 @@
while (ec->mEffects.size()) {
sp<EffectModule> effect = ec->mEffects[0];
effect->unPin();
- t->removeEffect_l(effect);
+ t->removeEffect_l(effect, /*release*/ true);
if (effect->purgeHandles()) {
t->checkSuspendOnEffectEnabled_l(effect, false, effect->sessionId());
}
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index f2c1c4f..bd5f146 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -25,6 +25,7 @@
#include <system/audio_effects/effect_ns.h>
#include <system/audio_effects/effect_visualizer.h>
#include <audio_utils/primitives.h>
+#include <media/AudioEffect.h>
#include <media/audiohal/EffectHalInterface.h>
#include <media/audiohal/EffectsFactoryHalInterface.h>
@@ -109,7 +110,10 @@
{
ALOGV("Destructor %p", this);
if (mEffectInterface != 0) {
- ALOGW("EffectModule %p destructor called with unreleased interface", this);
+ char uuidStr[64];
+ AudioEffect::guidToString(&mDescriptor.uuid, uuidStr, sizeof(uuidStr));
+ ALOGW("EffectModule %p destructor called with unreleased interface, effect %s",
+ this, uuidStr);
release_l();
}
@@ -1081,18 +1085,12 @@
result.append(buffer);
result.append("\t\tDescriptor:\n");
- snprintf(buffer, SIZE, "\t\t- UUID: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X\n",
- mDescriptor.uuid.timeLow, mDescriptor.uuid.timeMid, mDescriptor.uuid.timeHiAndVersion,
- mDescriptor.uuid.clockSeq, mDescriptor.uuid.node[0], mDescriptor.uuid.node[1],
- mDescriptor.uuid.node[2],
- mDescriptor.uuid.node[3],mDescriptor.uuid.node[4],mDescriptor.uuid.node[5]);
+ char uuidStr[64];
+ AudioEffect::guidToString(&mDescriptor.uuid, uuidStr, sizeof(uuidStr));
+ snprintf(buffer, SIZE, "\t\t- UUID: %s\n", uuidStr);
result.append(buffer);
- snprintf(buffer, SIZE, "\t\t- TYPE: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X\n",
- mDescriptor.type.timeLow, mDescriptor.type.timeMid,
- mDescriptor.type.timeHiAndVersion,
- mDescriptor.type.clockSeq, mDescriptor.type.node[0], mDescriptor.type.node[1],
- mDescriptor.type.node[2],
- mDescriptor.type.node[3],mDescriptor.type.node[4],mDescriptor.type.node[5]);
+ AudioEffect::guidToString(&mDescriptor.type, uuidStr, sizeof(uuidStr));
+ snprintf(buffer, SIZE, "\t\t- TYPE: %s\n", uuidStr);
result.append(buffer);
snprintf(buffer, SIZE, "\t\t- apiVersion: %08X\n\t\t- flags: %08X (%s)\n",
mDescriptor.apiVersion,
@@ -1306,11 +1304,10 @@
if (thread != 0) {
thread->disconnectEffectHandle(this, unpinIfLast);
} else {
- ALOGW("%s Effect handle %p disconnected after thread destruction", __FUNCTION__, this);
// try to cleanup as much as we can
sp<EffectModule> effect = mEffect.promote();
- if (effect != 0) {
- effect->disconnectHandle(this, unpinIfLast);
+ if (effect != 0 && effect->disconnectHandle(this, unpinIfLast) > 0) {
+ ALOGW("%s Effect handle %p disconnected after thread destruction", __FUNCTION__, this);
}
}
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index a16b123..e6ee6c9 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -4721,10 +4721,10 @@
// use device for strategy enforced audible
// 2: we are in call or the strategy phone is active on the output:
// use device for strategy phone
- // 3: the strategy for enforced audible is active but not enforced on the output:
- // use the device for strategy enforced audible
- // 4: the strategy sonification is active on the output:
+ // 3: the strategy sonification is active on the output:
// use device for strategy sonification
+ // 4: the strategy for enforced audible is active but not enforced on the output:
+ // use the device for strategy enforced audible
// 5: the strategy accessibility is active on the output:
// use device for strategy accessibility
// 6: the strategy "respectful" sonification is active on the output:
@@ -4741,10 +4741,10 @@
} else if (isInCall() ||
isStrategyActive(outputDesc, STRATEGY_PHONE)) {
device = getDeviceForStrategy(STRATEGY_PHONE, fromCache);
- } else if (isStrategyActive(outputDesc, STRATEGY_ENFORCED_AUDIBLE)) {
- device = getDeviceForStrategy(STRATEGY_ENFORCED_AUDIBLE, fromCache);
} else if (isStrategyActive(outputDesc, STRATEGY_SONIFICATION)) {
device = getDeviceForStrategy(STRATEGY_SONIFICATION, fromCache);
+ } else if (isStrategyActive(outputDesc, STRATEGY_ENFORCED_AUDIBLE)) {
+ device = getDeviceForStrategy(STRATEGY_ENFORCED_AUDIBLE, fromCache);
} else if (isStrategyActive(outputDesc, STRATEGY_ACCESSIBILITY)) {
device = getDeviceForStrategy(STRATEGY_ACCESSIBILITY, fromCache);
} else if (isStrategyActive(outputDesc, STRATEGY_SONIFICATION_RESPECTFUL)) {
diff --git a/services/mediaanalytics/MediaAnalyticsService.cpp b/services/mediaanalytics/MediaAnalyticsService.cpp
index f3bb35c..3c39883 100644
--- a/services/mediaanalytics/MediaAnalyticsService.cpp
+++ b/services/mediaanalytics/MediaAnalyticsService.cpp
@@ -29,12 +29,15 @@
#include <unistd.h>
#include <string.h>
+#include <pwd.h>
#include <cutils/atomic.h>
#include <cutils/properties.h> // for property_get
#include <utils/misc.h>
+#include <android/content/pm/IPackageManagerNative.h>
+
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/MemoryHeapBase.h>
@@ -80,27 +83,26 @@
namespace android {
+ using namespace android::base;
+ using namespace android::content::pm;
+
// summarized records
-// up to 48 sets, each covering an hour -- at least 2 days of coverage
+// up to 36 sets, each covering an hour -- so at least 1.5 days
// (will be longer if there are hours without any media action)
static const nsecs_t kNewSetIntervalNs = 3600*(1000*1000*1000ll);
-static const int kMaxRecordSets = 48;
-// individual records kept in memory
-static const int kMaxRecords = 100;
+static const int kMaxRecordSets = 36;
+// individual records kept in memory: age or count
+// age: <= 36 hours (1.5 days)
+// count: hard limit of # records
+// (0 for either of these disables that threshold)
+static const nsecs_t kMaxRecordAgeNs = 36 * 3600 * (1000*1000*1000ll);
+static const int kMaxRecords = 0;
static const char *kServiceName = "media.metrics";
-
-//using android::status_t;
-//using android::OK;
-//using android::BAD_VALUE;
-//using android::NOT_ENOUGH_DATA;
-//using android::Parcel;
-
-
void MediaAnalyticsService::instantiate() {
defaultServiceManager()->addService(
String16(kServiceName), new MediaAnalyticsService());
@@ -110,6 +112,7 @@
MediaAnalyticsService::SummarizerSet::SummarizerSet() {
mSummarizers = new List<MetricsSummarizer *>();
}
+
MediaAnalyticsService::SummarizerSet::~SummarizerSet() {
// empty the list
List<MetricsSummarizer *> *l = mSummarizers;
@@ -153,8 +156,10 @@
MediaAnalyticsService::MediaAnalyticsService()
: mMaxRecords(kMaxRecords),
+ mMaxRecordAgeNs(kMaxRecordAgeNs),
mMaxRecordSets(kMaxRecordSets),
- mNewSetInterval(kNewSetIntervalNs) {
+ mNewSetInterval(kNewSetIntervalNs),
+ mDumpProto(MediaAnalyticsItem::PROTO_V0) {
ALOGD("MediaAnalyticsService created");
// clear our queues
@@ -167,6 +172,8 @@
mItemsSubmitted = 0;
mItemsFinalized = 0;
mItemsDiscarded = 0;
+ mItemsDiscardedExpire = 0;
+ mItemsDiscardedCount = 0;
mLastSessionID = 0;
// recover any persistency we set up
@@ -177,8 +184,23 @@
ALOGD("MediaAnalyticsService destroyed");
// clean out mOpen and mFinalized
+ while (mOpen->size() > 0) {
+ MediaAnalyticsItem * oitem = *(mOpen->begin());
+ mOpen->erase(mOpen->begin());
+ delete oitem;
+ mItemsDiscarded++;
+ mItemsDiscardedCount++;
+ }
delete mOpen;
mOpen = NULL;
+
+ while (mFinalized->size() > 0) {
+ MediaAnalyticsItem * oitem = *(mFinalized->begin());
+ mFinalized->erase(mFinalized->begin());
+ delete oitem;
+ mItemsDiscarded++;
+ mItemsDiscardedCount++;
+ }
delete mFinalized;
mFinalized = NULL;
@@ -212,13 +234,15 @@
//
bool isTrusted = false;
+ ALOGV("caller has uid=%d, embedded uid=%d", uid, uid_given);
+
switch (uid) {
case AID_MEDIA:
case AID_MEDIA_CODEC:
case AID_MEDIA_EX:
case AID_MEDIA_DRM:
// trusted source, only override default values
- isTrusted = true;
+ isTrusted = true;
if (uid_given == (-1)) {
item->setUid(uid);
}
@@ -233,6 +257,9 @@
break;
}
+ item->setPkgName(getPkgName(item->getUid(), true));
+ item->setPkgVersionCode(0);
+ ALOGD("info is from uid %d pkg '%s', version %d", item->getUid(), item->getPkgName().c_str(), item->getPkgVersionCode());
mItemsSubmitted++;
@@ -316,6 +343,7 @@
return id;
}
+
status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
{
const size_t SIZE = 512;
@@ -333,22 +361,41 @@
}
// crack any parameters
- bool clear = false;
- bool summary = false;
- nsecs_t ts_since = 0;
String16 summaryOption("-summary");
+ bool summary = false;
+ String16 protoOption("-proto");
String16 clearOption("-clear");
+ bool clear = false;
String16 sinceOption("-since");
+ nsecs_t ts_since = 0;
String16 helpOption("-help");
String16 onlyOption("-only");
- const char *only = NULL;
+ AString only;
int n = args.size();
+
for (int i = 0; i < n; i++) {
String8 myarg(args[i]);
if (args[i] == clearOption) {
clear = true;
} else if (args[i] == summaryOption) {
summary = true;
+ } else if (args[i] == protoOption) {
+ i++;
+ if (i < n) {
+ String8 value(args[i]);
+ int proto = MediaAnalyticsItem::PROTO_V0; // default to original
+ 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;
+ }
+ mDumpProto = proto;
+ }
+ }
} else if (args[i] == sinceOption) {
i++;
if (i < n) {
@@ -368,18 +415,12 @@
i++;
if (i < n) {
String8 value(args[i]);
- const char *p = value.string();
- char *q = strdup(p);
- if (q != NULL) {
- if (only != NULL) {
- free((void*)only);
- }
- only = q;
- }
+ only = value.string();
}
} else if (args[i] == helpOption) {
result.append("Recognized parameters:\n");
result.append("-help this help message\n");
+ result.append("-proto X dump using protocol X (defaults to 1)");
result.append("-summary show summary info\n");
result.append("-clear clears out saved records\n");
result.append("-only X process records for component X\n");
@@ -398,14 +439,12 @@
dumpHeaders(result, ts_since);
- // only want 1, to avoid confusing folks that parse the output
+ // want exactly 1, to avoid confusing folks that parse the output
if (summary) {
- dumpSummaries(result, ts_since, only);
+ dumpSummaries(result, ts_since, only.c_str());
} else {
- dumpRecent(result, ts_since, only);
+ dumpRecent(result, ts_since, only.c_str());
}
- free((void*)only);
- only=NULL;
if (clear) {
@@ -430,6 +469,9 @@
const size_t SIZE = 512;
char buffer[SIZE];
+ snprintf(buffer, SIZE, "Protocol Version: %d\n", mDumpProto);
+ result.append(buffer);
+
int enabled = MediaAnalyticsItem::isEnabled();
if (enabled) {
snprintf(buffer, SIZE, "Metrics gathering: enabled\n");
@@ -439,10 +481,14 @@
result.append(buffer);
snprintf(buffer, SIZE,
- "Since Boot: Submissions: %" PRId64
- " Finalizations: %" PRId64
- " Discarded: %" PRId64 "\n",
- mItemsSubmitted, mItemsFinalized, mItemsDiscarded);
+ "Since Boot: Submissions: %8" PRId64
+ " Finalizations: %8" PRId64 "\n",
+ mItemsSubmitted, mItemsFinalized);
+ result.append(buffer);
+ snprintf(buffer, SIZE,
+ "Records Discarded: %8" PRId64
+ " (by Count: %" PRId64 " by Expiration: %" PRId64 ")\n",
+ mItemsDiscarded, mItemsDiscardedCount, mItemsDiscardedExpire);
result.append(buffer);
snprintf(buffer, SIZE,
"Summary Sets Discarded: %" PRId64 "\n", mSetsDiscarded);
@@ -464,6 +510,10 @@
snprintf(buffer, SIZE, "\nSummarized Metrics:\n");
result.append(buffer);
+ if (only != NULL && *only == '\0') {
+ only = NULL;
+ }
+
// have each of the distillers dump records
if (mSummarizerSets != NULL) {
List<SummarizerSet *>::iterator itSet = mSummarizerSets->begin();
@@ -490,6 +540,10 @@
const size_t SIZE = 512;
char buffer[SIZE];
+ if (only != NULL && *only == '\0') {
+ only = NULL;
+ }
+
// show the recently recorded records
snprintf(buffer, sizeof(buffer), "\nFinalized Metrics (oldest first):\n");
result.append(buffer);
@@ -526,7 +580,7 @@
ALOGV("Omit '%s', it's not '%s'", (*it)->getKey().c_str(), only);
continue;
}
- AString entry = (*it)->toString();
+ AString entry = (*it)->toString(mDumpProto);
result.appendFormat("%5d: %s\n", slot, entry.c_str());
slot++;
}
@@ -551,13 +605,35 @@
l->push_back(item);
}
- // keep removing old records the front until we're in-bounds
+ // keep removing old records the front until we're in-bounds (count)
if (mMaxRecords > 0) {
while (l->size() > (size_t) mMaxRecords) {
MediaAnalyticsItem * oitem = *(l->begin());
l->erase(l->begin());
delete oitem;
mItemsDiscarded++;
+ mItemsDiscardedCount++;
+ }
+ }
+
+ // keep removing old records the front until we're in-bounds (count)
+ if (mMaxRecordAgeNs > 0) {
+ nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+ while (l->size() > 0) {
+ MediaAnalyticsItem * oitem = *(l->begin());
+ nsecs_t when = oitem->getTimestamp();
+// DEBUGGING -- remove this ALOGD() call
+ if(0) ALOGD("@ now=%10" PRId64 " record when=%10" PRId64 "",
+ now, when);
+ // careful about timejumps too
+ if ((now > when) && (now-when) <= mMaxRecordAgeNs) {
+ // this (and the rest) are recent enough to keep
+ break;
+ }
+ l->erase(l->begin());
+ delete oitem;
+ mItemsDiscarded++;
+ mItemsDiscardedExpire++;
}
}
}
@@ -720,4 +796,85 @@
}
+// mapping uids to package names
+
+// give me the package name, perhaps going to find it
+AString MediaAnalyticsService::getPkgName(uid_t uid, bool addIfMissing) {
+ ssize_t i = mPkgMappings.indexOfKey(uid);
+ if (i >= 0) {
+ AString pkg = mPkgMappings.valueAt(i);
+ ALOGV("returning pkg '%s' for uid %d", pkg.c_str(), uid);
+ return pkg;
+ }
+
+ AString pkg;
+
+ if (addIfMissing == false) {
+ return pkg;
+ }
+
+ struct passwd *pw = getpwuid(uid);
+ if (pw) {
+ pkg = pw->pw_name;
+ } else {
+ pkg = "-";
+ }
+
+ // find the proper value
+
+ sp<IBinder> binder = NULL;
+ sp<IServiceManager> sm = defaultServiceManager();
+ if (sm == NULL) {
+ ALOGE("defaultServiceManager failed");
+ } else {
+ binder = sm->getService(String16("package_native"));
+ if (binder == NULL) {
+ ALOGE("getService package_native failed");
+ }
+ }
+
+ if (binder != NULL) {
+ sp<IPackageManagerNative> package_mgr = interface_cast<IPackageManagerNative>(binder);
+
+ 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("package_native::getNamesForUids failed: %s",
+ status.exceptionMessage().c_str());
+ } else {
+ if (!names[0].empty()) {
+ pkg = names[0].c_str();
+ }
+ }
+ }
+
+ // XXX determine whether package was side-loaded or from playstore.
+ // for privacy, we only list apps loaded from playstore.
+
+ // Sanitize the package name for ":"
+ // as an example, we get "shared:android.uid.systemui"
+ // replace : with something benign (I'm going to use !)
+ if (!pkg.empty()) {
+ int n = pkg.size();
+ char *p = (char *) pkg.c_str();
+ for (int i = 0 ; i < n; i++) {
+ if (p[i] == ':') {
+ p[i] = '!';
+ }
+ }
+ }
+
+ // add it to the map, to save a subsequent lookup
+ if (!pkg.empty()) {
+ ALOGV("Adding uid %d pkg '%s'", uid, pkg.c_str());
+ mPkgMappings.add(uid, pkg);
+ }
+
+ return pkg;
+}
+
} // namespace android
diff --git a/services/mediaanalytics/MediaAnalyticsService.h b/services/mediaanalytics/MediaAnalyticsService.h
index 6685967..4fe2fb2 100644
--- a/services/mediaanalytics/MediaAnalyticsService.h
+++ b/services/mediaanalytics/MediaAnalyticsService.h
@@ -54,6 +54,8 @@
int64_t mItemsSubmitted;
int64_t mItemsFinalized;
int64_t mItemsDiscarded;
+ int64_t mItemsDiscardedExpire;
+ int64_t mItemsDiscardedCount;
int64_t mSetsDiscarded;
MediaAnalyticsItem::SessionID_t mLastSessionID;
@@ -61,9 +63,12 @@
mutable Mutex mLock;
mutable Mutex mLock_ids;
- // the most we hold in memory
- // up to this many in each queue (open, finalized)
+ // limit how many records we'll retain
+ // by count (in each queue (open, finalized))
int32_t mMaxRecords;
+ // by time (none older than this long agan
+ nsecs_t mMaxRecordAgeNs;
+ //
// # of sets of summaries
int32_t mMaxRecordSets;
// nsecs until we start a new record set
@@ -118,6 +123,7 @@
void deleteItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *);
// support for generating output
+ int mDumpProto;
String8 dumpQueue(List<MediaAnalyticsItem*> *);
String8 dumpQueue(List<MediaAnalyticsItem*> *, nsecs_t, const char *only);
@@ -125,6 +131,15 @@
void dumpSummaries(String8 &result, nsecs_t ts_since, const char * only);
void dumpRecent(String8 &result, nsecs_t ts_since, const char * only);
+ // mapping uids to package names
+ struct UidToPkgMap {
+ uid_t uid;
+ AString pkg;
+ };
+
+ KeyedVector<uid_t,AString> mPkgMappings;
+ AString getPkgName(uid_t uid, bool addIfMissing);
+
};
// ----------------------------------------------------------------------------
diff --git a/services/mediaanalytics/MetricsSummarizer.cpp b/services/mediaanalytics/MetricsSummarizer.cpp
index 3477f1f..93fe0ec 100644
--- a/services/mediaanalytics/MetricsSummarizer.cpp
+++ b/services/mediaanalytics/MetricsSummarizer.cpp
@@ -141,23 +141,23 @@
List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
for (; it != mSummaries->end(); it++) {
bool good = sameAttributes((*it), item, getIgnorables());
- ALOGV("Match against %s says %d",
- (*it)->toString().c_str(), good);
+ ALOGV("Match against %s says %d", (*it)->toString().c_str(), good);
if (good)
break;
}
if (it == mSummaries->end()) {
ALOGV("save new record");
- item = item->dup();
- if (item == NULL) {
+ MediaAnalyticsItem *nitem = item->dup();
+ if (nitem == NULL) {
ALOGE("unable to save MediaMetrics record");
}
- sortProps(item);
- item->setInt32("count",1);
- mSummaries->push_back(item);
+ sortProps(nitem);
+ nitem->setInt32("aggregated",1);
+ mergeRecord(*nitem, *item);
+ mSummaries->push_back(nitem);
} else {
ALOGV("increment existing record");
- (*it)->addInt32("count",1);
+ (*it)->addInt32("aggregated",1);
mergeRecord(*(*it), *item);
}
}
@@ -168,6 +168,71 @@
return;
}
+// keep some stats for things: sums, counts, standard deviation
+// the integer version -- all of these pieces are in 64 bits
+void MetricsSummarizer::minMaxVar64(MediaAnalyticsItem &summation, const char *key, int64_t value) {
+ if (key == NULL)
+ return;
+ int len = strlen(key) + 32;
+ char *tmpKey = (char *)malloc(len);
+
+ if (tmpKey == NULL) {
+ return;
+ }
+
+ // N - count of samples
+ snprintf(tmpKey, len, "%s.n", key);
+ summation.addInt64(tmpKey, 1);
+
+ // zero - count of samples that are zero
+ if (value == 0) {
+ snprintf(tmpKey, len, "%s.zero", key);
+ int64_t zero = 0;
+ (void) summation.getInt64(tmpKey,&zero);
+ zero++;
+ summation.setInt64(tmpKey, zero);
+ }
+
+ // min
+ snprintf(tmpKey, len, "%s.min", key);
+ int64_t min = value;
+ if (summation.getInt64(tmpKey,&min)) {
+ if (min > value) {
+ summation.setInt64(tmpKey, value);
+ }
+ } else {
+ summation.setInt64(tmpKey, value);
+ }
+
+ // max
+ snprintf(tmpKey, len, "%s.max", key);
+ int64_t max = value;
+ if (summation.getInt64(tmpKey,&max)) {
+ if (max < value) {
+ summation.setInt64(tmpKey, value);
+ }
+ } else {
+ summation.setInt64(tmpKey, value);
+ }
+
+ // components for mean, stddev;
+ // stddev = sqrt(1/4*(sumx2 - (2*sumx*sumx/n) + ((sumx/n)^2)))
+ // sum x
+ snprintf(tmpKey, len, "%s.sumX", key);
+ summation.addInt64(tmpKey, value);
+ // sum x^2
+ snprintf(tmpKey, len, "%s.sumX2", key);
+ summation.addInt64(tmpKey, value*value);
+
+
+ // last thing we do -- remove the base key from the summation
+ // record so we won't get confused about it having both individual
+ // and summary information in there.
+ summation.removeProp(key);
+
+ free(tmpKey);
+}
+
//
// Comparators
@@ -186,20 +251,23 @@
ALOGV("MetricsSummarizer::sameAttributes(): summ %s", summ->toString().c_str());
ALOGV("MetricsSummarizer::sameAttributes(): single %s", single->toString().c_str());
+ // keep different sources/users separate
+ if (single->mUid != summ->mUid) {
+ return false;
+ }
+
// this can be made better.
for(size_t i=0;i<single->mPropCount;i++) {
MediaAnalyticsItem::Prop *prop1 = &(single->mProps[i]);
const char *attrName = prop1->mName;
- ALOGV("compare on attr '%s'", attrName);
// is it something we should ignore
if (ignorable != NULL) {
const char **ig = ignorable;
- while (*ig) {
+ for (;*ig; ig++) {
if (strcmp(*ig, attrName) == 0) {
break;
}
- ig++;
}
if (*ig) {
ALOGV("we don't mind that it has attr '%s'", attrName);
@@ -218,29 +286,42 @@
}
switch (prop1->mType) {
case MediaAnalyticsItem::kTypeInt32:
- if (prop1->u.int32Value != prop2->u.int32Value)
+ if (prop1->u.int32Value != prop2->u.int32Value) {
+ ALOGV("mismatch values");
return false;
+ }
break;
case MediaAnalyticsItem::kTypeInt64:
- if (prop1->u.int64Value != prop2->u.int64Value)
+ if (prop1->u.int64Value != prop2->u.int64Value) {
+ ALOGV("mismatch values");
return false;
+ }
break;
case MediaAnalyticsItem::kTypeDouble:
// XXX: watch out for floating point comparisons!
- if (prop1->u.doubleValue != prop2->u.doubleValue)
+ if (prop1->u.doubleValue != prop2->u.doubleValue) {
+ ALOGV("mismatch values");
return false;
+ }
break;
case MediaAnalyticsItem::kTypeCString:
- if (strcmp(prop1->u.CStringValue, prop2->u.CStringValue) != 0)
+ if (strcmp(prop1->u.CStringValue, prop2->u.CStringValue) != 0) {
+ ALOGV("mismatch values");
return false;
+ }
break;
case MediaAnalyticsItem::kTypeRate:
- if (prop1->u.rate.count != prop2->u.rate.count)
+ if (prop1->u.rate.count != prop2->u.rate.count) {
+ ALOGV("mismatch values");
return false;
- if (prop1->u.rate.duration != prop2->u.rate.duration)
+ }
+ if (prop1->u.rate.duration != prop2->u.rate.duration) {
+ ALOGV("mismatch values");
return false;
+ }
break;
default:
+ ALOGV("mismatch values in default type");
return false;
}
}
@@ -248,15 +329,6 @@
return true;
}
-bool MetricsSummarizer::sameAttributesId(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
-
- // verify same user
- if (summ->mPid != single->mPid)
- return false;
-
- // and finally do the more expensive validation of the attributes
- return sameAttributes(summ, single, ignorable);
-}
int MetricsSummarizer::PropSorter(const void *a, const void *b) {
MediaAnalyticsItem::Prop *ai = (MediaAnalyticsItem::Prop *)a;
@@ -267,14 +339,8 @@
// we sort in the summaries so that it looks pretty in the dumpsys
void MetricsSummarizer::sortProps(MediaAnalyticsItem *item) {
if (item->mPropCount != 0) {
- if (DEBUG_SORT) {
- ALOGD("sortProps(pre): %s", item->toString().c_str());
- }
qsort(item->mProps, item->mPropCount,
sizeof(MediaAnalyticsItem::Prop), MetricsSummarizer::PropSorter);
- if (DEBUG_SORT) {
- ALOGD("sortProps(pst): %s", item->toString().c_str());
- }
}
}
diff --git a/services/mediaanalytics/MetricsSummarizer.h b/services/mediaanalytics/MetricsSummarizer.h
index 0b64eac..a9f0786 100644
--- a/services/mediaanalytics/MetricsSummarizer.h
+++ b/services/mediaanalytics/MetricsSummarizer.h
@@ -59,10 +59,9 @@
// various comparators
// "do these records have same attributes and values in those attrs"
- // ditto, but watch for "error" fields
bool sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignoreables);
- // attributes + from the same app/userid
- bool sameAttributesId(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignoreables);
+
+ void minMaxVar64(MediaAnalyticsItem &summ, const char *key, int64_t value);
static int PropSorter(const void *a, const void *b);
void sortProps(MediaAnalyticsItem *item);
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.cpp b/services/mediaanalytics/MetricsSummarizerPlayer.cpp
index 5162059..f882cb9 100644
--- a/services/mediaanalytics/MetricsSummarizerPlayer.cpp
+++ b/services/mediaanalytics/MetricsSummarizerPlayer.cpp
@@ -51,37 +51,43 @@
setIgnorables(player_ignorable);
}
+// NB: this is also called for the first time -- so summation == item
+// Not sure if we need a flag for that or not.
+// In this particular mergeRecord() code -- we're' ok for this.
void MetricsSummarizerPlayer::mergeRecord(MediaAnalyticsItem &summation, MediaAnalyticsItem &item) {
ALOGV("MetricsSummarizerPlayer::mergeRecord()");
- //
- // we sum time & frames.
- // be careful about our special "-1" values that indicate 'unknown'
- // treat those as 0 [basically, not summing them into the totals].
+
int64_t duration = 0;
if (item.getInt64("android.media.mediaplayer.durationMs", &duration)) {
ALOGV("found durationMs of %" PRId64, duration);
- summation.addInt64("android.media.mediaplayer.durationMs",duration);
+ minMaxVar64(summation, "android.media.mediaplayer.durationMs", duration);
}
+
int64_t playing = 0;
- if (item.getInt64("android.media.mediaplayer.playingMs", &playing))
+ if (item.getInt64("android.media.mediaplayer.playingMs", &playing)) {
ALOGV("found playingMs of %" PRId64, playing);
- if (playing >= 0) {
- summation.addInt64("android.media.mediaplayer.playingMs",playing);
- }
+ }
+ if (playing >= 0) {
+ minMaxVar64(summation,"android.media.mediaplayer.playingMs",playing);
+ }
+
int64_t frames = 0;
- if (item.getInt64("android.media.mediaplayer.frames", &frames))
+ if (item.getInt64("android.media.mediaplayer.frames", &frames)) {
ALOGV("found framess of %" PRId64, frames);
- if (frames >= 0) {
- summation.addInt64("android.media.mediaplayer.frames",frames);
- }
+ }
+ if (frames >= 0) {
+ minMaxVar64(summation,"android.media.mediaplayer.frames",frames);
+ }
+
int64_t dropped = 0;
- if (item.getInt64("android.media.mediaplayer.dropped", &dropped))
+ if (item.getInt64("android.media.mediaplayer.dropped", &dropped)) {
ALOGV("found dropped of %" PRId64, dropped);
- if (dropped >= 0) {
- summation.addInt64("android.media.mediaplayer.dropped",dropped);
- }
+ }
+ if (dropped >= 0) {
+ minMaxVar64(summation,"android.media.mediaplayer.dropped",dropped);
+ }
}
} // namespace android