diff options
| author | 2019-10-23 16:54:53 -0700 | |
|---|---|---|
| committer | 2019-12-17 16:38:51 -0800 | |
| commit | f2310c70651049c41ffa65e93265f87c7dd3b88d (patch) | |
| tree | 3357958a91ca328afb35657a84828bb32027b1bb | |
| parent | 4b90597d3e0dd0fa1702af7edae7c0d6b887c833 (diff) | |
MediaMetrics: Add MediaMetrics Java interface
Test: Verify media.metrics dumpsys, atest MediaMetricsTest
Bug: 138583596
Change-Id: Ic0e86fcbb4768f9d21a32dae7e69e071a67d965d
| -rw-r--r-- | core/jni/AndroidRuntime.cpp | 2 | ||||
| -rw-r--r-- | media/java/android/media/MediaMetrics.java | 634 | ||||
| -rw-r--r-- | media/jni/android_media_MediaMetricsJNI.cpp | 38 |
3 files changed, 673 insertions, 1 deletions
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f7a994f7071f..3cde887ba465 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -129,6 +129,7 @@ extern int register_android_database_CursorWindow(JNIEnv* env); 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 @@ static const RegJNIRec gRegJNI[] = { 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 000000000000..88a829546989 --- /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 494c61721e02..e17a6173ba4d 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 @@ jobject MediaMetricsJNI::writeMetricsToBundle( 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 @@ jobject MediaMetricsJNI::nativeToJavaPersistableBundle(JNIEnv *env, 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 |