Merge "MediaMetrics: Add MediaMetrics Java interface"
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f7a994f..3cde887 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -129,6 +129,7 @@
extern int register_android_database_SQLiteConnection(JNIEnv* env);
extern int register_android_database_SQLiteGlobal(JNIEnv* env);
extern int register_android_database_SQLiteDebug(JNIEnv* env);
+extern int register_android_media_MediaMetrics(JNIEnv *env);
extern int register_android_os_Debug(JNIEnv* env);
extern int register_android_os_GraphicsEnvironment(JNIEnv* env);
extern int register_android_os_HidlSupport(JNIEnv* env);
@@ -1520,6 +1521,7 @@
REG_JNI(register_android_media_AudioProductStrategies),
REG_JNI(register_android_media_AudioVolumeGroups),
REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
+ REG_JNI(register_android_media_MediaMetrics),
REG_JNI(register_android_media_MicrophoneInfo),
REG_JNI(register_android_media_RemoteDisplay),
REG_JNI(register_android_media_ToneGenerator),
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java
new file mode 100644
index 0000000..88a8295
--- /dev/null
+++ b/media/java/android/media/MediaMetrics.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Bundle;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * MediaMetrics is the Java interface to the MediaMetrics service.
+ *
+ * This is used to collect media statistics by the framework.
+ * It is not intended for direct application use.
+ *
+ * @hide
+ */
+public class MediaMetrics {
+ public static final String TAG = "MediaMetrics";
+
+ /**
+ * The TYPE constants below should match those in native MediaMetricsItem.h
+ */
+ private static final int TYPE_NONE = 0;
+ private static final int TYPE_INT32 = 1; // Java integer
+ private static final int TYPE_INT64 = 2; // Java long
+ private static final int TYPE_DOUBLE = 3; // Java double
+ private static final int TYPE_CSTRING = 4; // Java string
+ private static final int TYPE_RATE = 5; // Two longs, ignored in Java
+
+ // The charset used for encoding Strings to bytes.
+ private static final Charset MEDIAMETRICS_CHARSET = StandardCharsets.UTF_8;
+
+ /**
+ * Item records properties and delivers to the MediaMetrics service
+ *
+ */
+ public static class Item {
+
+ /*
+ * MediaMetrics Item
+ *
+ * Creates a Byte String and sends to the MediaMetrics service.
+ * The Byte String serves as a compact form for logging data
+ * with low overhead for storage.
+ *
+ * The Byte String format is as follows:
+ *
+ * For Java
+ * int64 corresponds to long
+ * int32, uint32 corresponds to int
+ * uint16 corresponds to char
+ * uint8, int8 corresponds to byte
+ *
+ * For items transmitted from Java, uint8 and uint32 values are limited
+ * to INT8_MAX and INT32_MAX. This constrains the size of large items
+ * to 2GB, which is consistent with ByteBuffer max size. A native item
+ * can conceivably have size of 4GB.
+ *
+ * Physical layout of integers and doubles within the MediaMetrics byte string
+ * is in Native / host order, which is usually little endian.
+ *
+ * Note that primitive data (ints, doubles) within a Byte String has
+ * no extra padding or alignment requirements, like ByteBuffer.
+ *
+ * -- begin of item
+ * -- begin of header
+ * (uint32) item size: including the item size field
+ * (uint32) header size, including the item size and header size fields.
+ * (uint16) version: exactly 0
+ * (uint16) key size, that is key strlen + 1 for zero termination.
+ * (int8)+ key, a string which is 0 terminated (UTF-8).
+ * (int32) pid
+ * (int32) uid
+ * (int64) timestamp
+ * -- end of header
+ * -- begin body
+ * (uint32) number of properties
+ * -- repeat for number of properties
+ * (uint16) property size, including property size field itself
+ * (uint8) type of property
+ * (int8)+ key string, including 0 termination
+ * based on type of property (given above), one of:
+ * (int32)
+ * (int64)
+ * (double)
+ * (int8)+ for TYPE_CSTRING, including 0 termination
+ * (int64, int64) for rate
+ * -- end body
+ * -- end of item
+ *
+ * To record a MediaMetrics event, one creates a new item with an id,
+ * then use a series of puts to add properties
+ * and then a record() to send to the MediaMetrics service.
+ *
+ * The properties may not be unique, and putting a later property with
+ * the same name as an earlier property will overwrite the value and type
+ * of the prior property.
+ *
+ * The timestamp can only be recorded by a system service (and is ignored otherwise;
+ * the MediaMetrics service will fill in the timestamp as needed).
+ *
+ * The units of time are in SystemClock.elapsedRealtimeNanos().
+ *
+ * A clear() may be called to reset the properties to empty, the time to 0, but keep
+ * the other entries the same. This may be called after record().
+ * Additional properties may be added after calling record(). Changing the same property
+ * repeatedly is discouraged as - for this particular implementation - extra data
+ * is stored per change.
+ *
+ * new MediaMetrics.Item(mSomeId)
+ * .putString("event", "javaCreate")
+ * .putInt("value", intValue)
+ * .record();
+ */
+
+ /**
+ * Creates an Item with server added uid, time.
+ *
+ * This is the typical way to record a MediaMetrics item.
+ *
+ * @param key the Metrics ID associated with the item.
+ */
+ public Item(String key) {
+ this(key, -1 /* pid */, -1 /* uid */, 0 /* SystemClock.elapsedRealtimeNanos() */,
+ 2048 /* capacity */);
+ }
+
+ /**
+ * Creates an Item specifying pid, uid, time, and initial Item capacity.
+ *
+ * This might be used by a service to specify a different PID or UID for a client.
+ *
+ * @param key the Metrics ID associated with the item.
+ * An app may only set properties on an item which has already been
+ * logged previously by a service.
+ * @param pid the process ID corresponding to the item.
+ * A value of -1 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill this in.
+ * @param uid the user ID corresponding to the item.
+ * A value of -1 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill this in.
+ * @param timeNs the time when the item occurred (may be in the past).
+ * A value of 0 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill it in.
+ * Should be obtained from SystemClock.elapsedRealtimeNanos().
+ * @param capacity the anticipated size to use for the buffer.
+ * If the capacity is too small, the buffer will be resized to accommodate.
+ * This is amortized to copy data no more than twice.
+ */
+ public Item(String key, int pid, int uid, long timeNs, int capacity) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final int keyLength = keyBytes.length;
+ if (keyLength > Character.MAX_VALUE - 1) {
+ throw new IllegalArgumentException("Key length too large");
+ }
+
+ // Version 0 - compute the header offsets here.
+ mHeaderSize = 4 + 4 + 2 + 2 + keyLength + 1 + 4 + 4 + 8; // see format above.
+ mPidOffset = mHeaderSize - 16;
+ mUidOffset = mHeaderSize - 12;
+ mTimeNsOffset = mHeaderSize - 8;
+ mPropertyCountOffset = mHeaderSize;
+ mPropertyStartOffset = mHeaderSize + 4;
+
+ mKey = key;
+ mBuffer = ByteBuffer.allocateDirect(
+ Math.max(capacity, mHeaderSize + MINIMUM_PAYLOAD_SIZE));
+
+ // Version 0 - fill the ByteBuffer with the header (some details updated later).
+ mBuffer.order(ByteOrder.nativeOrder())
+ .putInt((int) 0) // total size in bytes (filled in later)
+ .putInt((int) mHeaderSize) // size of header
+ .putChar((char) FORMAT_VERSION) // version
+ .putChar((char) (keyLength + 1)) // length, with zero termination
+ .put(keyBytes).put((byte) 0)
+ .putInt(pid)
+ .putInt(uid)
+ .putLong(timeNs);
+ if (mHeaderSize != mBuffer.position()) {
+ throw new IllegalStateException("Mismatched sizing");
+ }
+ mBuffer.putInt(0); // number of properties (to be later filled in by record()).
+ }
+
+ /**
+ * Sets the property with key to an integer (32 bit) value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putInt(String key, int value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 4 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_INT32)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putInt(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a long (64 bit) value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putLong(String key, long value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_INT64)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putLong(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a double value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putDouble(String key, double value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_DOUBLE)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putDouble(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a String value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putString(String key, String value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final byte[] valueBytes = value.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, valueBytes.length + 1);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_CSTRING)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .put(valueBytes).put((byte) 0); // value, zero term.
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the pid to the provided value.
+ *
+ * @param pid which can be -1 if the service is to fill it in from the calling info.
+ * @return itself
+ */
+ public Item setPid(int pid) {
+ mBuffer.putInt(mPidOffset, pid); // pid location in byte string.
+ return this;
+ }
+
+ /**
+ * Sets the uid to the provided value.
+ *
+ * The UID represents the client associated with the property. This must be the UID
+ * of the application if it comes from the application client.
+ *
+ * Trusted services are allowed to set the uid for a client-related item.
+ *
+ * @param uid which can be -1 if the service is to fill it in from calling info.
+ * @return itself
+ */
+ public Item setUid(int uid) {
+ mBuffer.putInt(mUidOffset, uid); // uid location in byte string.
+ return this;
+ }
+
+ /**
+ * Sets the timestamp to the provided value.
+ *
+ * The time is referenced by the Boottime obtained by SystemClock.elapsedRealtimeNanos().
+ * This should be associated with the occurrence of the event. It is recommended that
+ * the event be registered immediately when it occurs, and no later than 500ms
+ * (and certainly not in the future).
+ *
+ * @param timeNs which can be 0 if the service is to fill it in at the time of call.
+ * @return itself
+ */
+ public Item setTimestamp(long timeNs) {
+ mBuffer.putLong(mTimeNsOffset, timeNs); // time location in byte string.
+ return this;
+ }
+
+ /**
+ * Clears the properties and resets the time to 0.
+ *
+ * No other values are changed.
+ *
+ * @return itself
+ */
+ public Item clear() {
+ mBuffer.position(mPropertyStartOffset);
+ mBuffer.limit(mBuffer.capacity());
+ mBuffer.putLong(mTimeNsOffset, 0); // reset time.
+ mPropertyCount = 0;
+ return this;
+ }
+
+ /**
+ * Sends the item to the MediaMetrics service.
+ *
+ * The item properties are unchanged, hence record() may be called more than once
+ * to send the same item twice. Also, record() may be called without any properties.
+ *
+ * @return true if successful.
+ */
+ public boolean record() {
+ updateHeader();
+ return native_submit_bytebuffer(mBuffer, mBuffer.limit()) >= 0;
+ }
+
+ /**
+ * Converts the Item to a Bundle.
+ *
+ * This is primarily used as a test API for CTS.
+ *
+ * @return a Bundle with the keys set according to data in the Item's buffer.
+ */
+ @TestApi
+ public Bundle toBundle() {
+ updateHeader();
+
+ final ByteBuffer buffer = mBuffer.duplicate();
+ buffer.order(ByteOrder.nativeOrder()) // restore order property
+ .flip(); // convert from write buffer to read buffer
+
+ return toBundle(buffer);
+ }
+
+ // The following constants are used for tests to extract
+ // the content of the Bundle for CTS testing.
+ @TestApi
+ public static final String BUNDLE_TOTAL_SIZE = "_totalSize";
+ @TestApi
+ public static final String BUNDLE_HEADER_SIZE = "_headerSize";
+ @TestApi
+ public static final String BUNDLE_VERSION = "_version";
+ @TestApi
+ public static final String BUNDLE_KEY_SIZE = "_keySize";
+ @TestApi
+ public static final String BUNDLE_KEY = "_key";
+ @TestApi
+ public static final String BUNDLE_PID = "_pid";
+ @TestApi
+ public static final String BUNDLE_UID = "_uid";
+ @TestApi
+ public static final String BUNDLE_TIMESTAMP = "_timestamp";
+ @TestApi
+ public static final String BUNDLE_PROPERTY_COUNT = "_propertyCount";
+
+ /**
+ * Converts a buffer contents to a bundle
+ *
+ * This is primarily used as a test API for CTS.
+ *
+ * @param buffer contains the byte data serialized according to the byte string version.
+ * @return a Bundle with the keys set according to data in the buffer.
+ */
+ @TestApi
+ public static Bundle toBundle(ByteBuffer buffer) {
+ final Bundle bundle = new Bundle();
+
+ final int totalSize = buffer.getInt();
+ final int headerSize = buffer.getInt();
+ final char version = buffer.getChar();
+ final char keySize = buffer.getChar(); // includes zero termination, i.e. keyLength + 1
+
+ if (totalSize < 0 || headerSize < 0) {
+ throw new IllegalArgumentException("Item size cannot be > " + Integer.MAX_VALUE);
+ }
+ final String key;
+ if (keySize > 0) {
+ key = getStringFromBuffer(buffer, keySize);
+ } else {
+ throw new IllegalArgumentException("Illegal null key");
+ }
+
+ final int pid = buffer.getInt();
+ final int uid = buffer.getInt();
+ final long timestamp = buffer.getLong();
+
+ // Verify header size (depending on version).
+ final int headerRead = buffer.position();
+ if (version == 0) {
+ if (headerRead != headerSize) {
+ throw new IllegalArgumentException(
+ "Item key:" + key
+ + " headerRead:" + headerRead + " != headerSize:" + headerSize);
+ }
+ } else {
+ // future versions should only increase header size
+ // by adding to the end.
+ if (headerRead > headerSize) {
+ throw new IllegalArgumentException(
+ "Item key:" + key
+ + " headerRead:" + headerRead + " > headerSize:" + headerSize);
+ } else if (headerRead < headerSize) {
+ buffer.position(headerSize);
+ }
+ }
+
+ // Body always starts with properties.
+ final int propertyCount = buffer.getInt();
+ if (propertyCount < 0) {
+ throw new IllegalArgumentException(
+ "Cannot have more than " + Integer.MAX_VALUE + " properties");
+ }
+ bundle.putInt(BUNDLE_TOTAL_SIZE, totalSize);
+ bundle.putInt(BUNDLE_HEADER_SIZE, headerSize);
+ bundle.putChar(BUNDLE_VERSION, version);
+ bundle.putChar(BUNDLE_KEY_SIZE, keySize);
+ bundle.putString(BUNDLE_KEY, key);
+ bundle.putInt(BUNDLE_PID, pid);
+ bundle.putInt(BUNDLE_UID, uid);
+ bundle.putLong(BUNDLE_TIMESTAMP, timestamp);
+ bundle.putInt(BUNDLE_PROPERTY_COUNT, propertyCount);
+
+ for (int i = 0; i < propertyCount; ++i) {
+ final int initialBufferPosition = buffer.position();
+ final char propSize = buffer.getChar();
+ final byte type = buffer.get();
+
+ // Log.d(TAG, "(" + i + ") propSize:" + ((int)propSize) + " type:" + type);
+ final String propKey = getStringFromBuffer(buffer);
+ switch (type) {
+ case TYPE_INT32:
+ bundle.putInt(propKey, buffer.getInt());
+ break;
+ case TYPE_INT64:
+ bundle.putLong(propKey, buffer.getLong());
+ break;
+ case TYPE_DOUBLE:
+ bundle.putDouble(propKey, buffer.getDouble());
+ break;
+ case TYPE_CSTRING:
+ bundle.putString(propKey, getStringFromBuffer(buffer));
+ break;
+ case TYPE_NONE:
+ break; // ignore on Java side
+ case TYPE_RATE:
+ buffer.getLong(); // consume the first int64_t of rate
+ buffer.getLong(); // consume the second int64_t of rate
+ break; // ignore on Java side
+ default:
+ // These are unsupported types for version 0
+ // We ignore them if the version is greater than 0.
+ if (version == 0) {
+ throw new IllegalArgumentException(
+ "Property " + propKey + " has unsupported type " + type);
+ }
+ buffer.position(initialBufferPosition + propSize); // advance and skip
+ break;
+ }
+ final int deltaPosition = buffer.position() - initialBufferPosition;
+ if (deltaPosition != propSize) {
+ throw new IllegalArgumentException("propSize:" + propSize
+ + " != deltaPosition:" + deltaPosition);
+ }
+ }
+
+ final int finalPosition = buffer.position();
+ if (finalPosition != totalSize) {
+ throw new IllegalArgumentException("totalSize:" + totalSize
+ + " != finalPosition:" + finalPosition);
+ }
+ return bundle;
+ }
+
+ // Version 0 byte offsets for the header.
+ private static final int FORMAT_VERSION = 0;
+ private static final int TOTAL_SIZE_OFFSET = 0;
+ private static final int HEADER_SIZE_OFFSET = 4;
+ private static final int MINIMUM_PAYLOAD_SIZE = 4;
+ private final int mPidOffset; // computed in constructor
+ private final int mUidOffset; // computed in constructor
+ private final int mTimeNsOffset; // computed in constructor
+ private final int mPropertyCountOffset; // computed in constructor
+ private final int mPropertyStartOffset; // computed in constructor
+ private final int mHeaderSize; // computed in constructor
+
+ private final String mKey;
+
+ private ByteBuffer mBuffer; // may be reallocated if capacity is insufficient.
+ private int mPropertyCount = 0; // overflow not checked (mBuffer would overflow first).
+
+ private int reserveProperty(byte[] keyBytes, int payloadSize) {
+ final int keyLength = keyBytes.length;
+ if (keyLength > Character.MAX_VALUE) {
+ throw new IllegalStateException("property key too long "
+ + new String(keyBytes, MEDIAMETRICS_CHARSET));
+ }
+ if (payloadSize > Character.MAX_VALUE) {
+ throw new IllegalStateException("payload too large " + payloadSize);
+ }
+
+ // See the byte string property format above.
+ final int size = 2 /* length */
+ + 1 /* type */
+ + keyLength + 1 /* key length with zero termination */
+ + payloadSize; /* payload size */
+
+ if (size > Character.MAX_VALUE) {
+ throw new IllegalStateException("Item property "
+ + new String(keyBytes, MEDIAMETRICS_CHARSET) + " is too large to send");
+ }
+
+ if (mBuffer.remaining() < size) {
+ int newCapacity = mBuffer.position() + size;
+ if (newCapacity > Integer.MAX_VALUE >> 1) {
+ throw new IllegalStateException(
+ "Item memory requirements too large: " + newCapacity);
+ }
+ newCapacity <<= 1;
+ ByteBuffer buffer = ByteBuffer.allocateDirect(newCapacity);
+ buffer.order(ByteOrder.nativeOrder());
+
+ // Copy data from old buffer to new buffer.
+ mBuffer.flip();
+ buffer.put(mBuffer);
+
+ // set buffer to new buffer
+ mBuffer = buffer;
+ }
+ return size;
+ }
+
+ // Used for test
+ private static String getStringFromBuffer(ByteBuffer buffer) {
+ return getStringFromBuffer(buffer, Integer.MAX_VALUE);
+ }
+
+ // Used for test
+ private static String getStringFromBuffer(ByteBuffer buffer, int size) {
+ int i = buffer.position();
+ int limit = buffer.limit();
+ if (size < Integer.MAX_VALUE - i && i + size < limit) {
+ limit = i + size;
+ }
+ for (; i < limit; ++i) {
+ if (buffer.get(i) == 0) {
+ final int newPosition = i + 1;
+ if (size != Integer.MAX_VALUE && newPosition - buffer.position() != size) {
+ throw new IllegalArgumentException("chars consumed at " + i + ": "
+ + (newPosition - buffer.position()) + " != size: " + size);
+ }
+ final String found;
+ if (buffer.hasArray()) {
+ found = new String(
+ buffer.array(), buffer.position() + buffer.arrayOffset(),
+ i - buffer.position(), MEDIAMETRICS_CHARSET);
+ buffer.position(newPosition);
+ } else {
+ final byte[] array = new byte[i - buffer.position()];
+ buffer.get(array);
+ found = new String(array, MEDIAMETRICS_CHARSET);
+ buffer.get(); // remove 0.
+ }
+ return found;
+ }
+ }
+ throw new IllegalArgumentException(
+ "No zero termination found in string position: "
+ + buffer.position() + " end: " + i);
+ }
+
+ /**
+ * May be called multiple times - just makes the header consistent with the current
+ * properties written.
+ */
+ private void updateHeader() {
+ // Buffer sized properly in constructor.
+ mBuffer.putInt(TOTAL_SIZE_OFFSET, mBuffer.position()) // set total length
+ .putInt(mPropertyCountOffset, (char) mPropertyCount); // set number of properties
+ }
+ }
+
+ private static native int native_submit_bytebuffer(@NonNull ByteBuffer buffer, int length);
+}
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index 494c617..e17a617 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -23,6 +23,7 @@
#include "android_media_MediaMetricsJNI.h"
#include "android_os_Parcel.h"
+#include "android_runtime/AndroidRuntime.h"
// This source file is compiled and linked into:
// core/jni/ (libandroid_runtime.so)
@@ -124,6 +125,28 @@
return bh.bundle;
}
+// Implementation of MediaMetrics.native_submit_bytebuffer(),
+// Delivers the byte buffer to the mediametrics service.
+static jint android_media_MediaMetrics_submit_bytebuffer(
+ JNIEnv* env, jobject thiz, jobject byteBuffer, jint length)
+{
+ const jbyte* buffer =
+ reinterpret_cast<const jbyte*>(env->GetDirectBufferAddress(byteBuffer));
+ if (buffer == nullptr) {
+ ALOGE("Error retrieving source of audio data to play, can't play");
+ return (jint)BAD_VALUE;
+ }
+
+ // TODO: directly record item to MediaMetrics service.
+ mediametrics::Item item;
+ if (item.readFromByteString((char *)buffer, length) != NO_ERROR) {
+ ALOGW("%s: cannot read from byte string", __func__);
+ return (jint)BAD_VALUE;
+ }
+ item.selfrecord();
+ return (jint)NO_ERROR;
+}
+
// Helper function to convert a native PersistableBundle to a Java
// PersistableBundle.
jobject MediaMetricsJNI::nativeToJavaPersistableBundle(JNIEnv *env,
@@ -191,5 +214,18 @@
return newBundle;
}
-}; // namespace android
+// ----------------------------------------------------------------------------
+static constexpr JNINativeMethod gMethods[] = {
+ {"native_submit_bytebuffer", "(Ljava/nio/ByteBuffer;I)I",
+ (void *)android_media_MediaMetrics_submit_bytebuffer},
+};
+
+// Registers the native methods, called from core/jni/AndroidRuntime.cpp
+int register_android_media_MediaMetrics(JNIEnv *env)
+{
+ return AndroidRuntime::registerNativeMethods(
+ env, "android/media/MediaMetrics", gMethods, std::size(gMethods));
+}
+
+}; // namespace android