| /* |
| * Copyright 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. |
| */ |
| |
| #define LOG_TAG "MediaMetricsJNI" |
| |
| #include <binder/Parcel.h> |
| #include <jni.h> |
| #include <media/MediaMetricsItem.h> |
| #include <nativehelper/JNIHelp.h> |
| #include <variant> |
| |
| #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) |
| |
| namespace android { |
| |
| namespace { |
| struct BundleHelper { |
| BundleHelper(JNIEnv* _env, jobject _bundle) |
| : env(_env) |
| , clazzBundle(env->FindClass("android/os/PersistableBundle")) |
| , putIntID(env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V")) |
| , putLongID(env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V")) |
| , putDoubleID(env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V")) |
| , putStringID(env->GetMethodID(clazzBundle, |
| "putString", "(Ljava/lang/String;Ljava/lang/String;)V")) |
| , constructID(env->GetMethodID(clazzBundle, "<init>", "()V")) |
| , bundle(_bundle == nullptr ? env->NewObject(clazzBundle, constructID) : _bundle) |
| { } |
| |
| JNIEnv* const env; |
| const jclass clazzBundle; |
| const jmethodID putIntID; |
| const jmethodID putLongID; |
| const jmethodID putDoubleID; |
| const jmethodID putStringID; |
| const jmethodID constructID; |
| jobject const bundle; |
| |
| // We use templated put to access mediametrics::Item based on data type not type enum. |
| // See std::variant and std::visit. |
| template<typename T> |
| void put(jstring keyName, const T& value) = delete; |
| |
| template<> |
| void put(jstring keyName, const int32_t& value) { |
| env->CallVoidMethod(bundle, putIntID, keyName, (jint)value); |
| } |
| |
| template<> |
| void put(jstring keyName, const int64_t& value) { |
| env->CallVoidMethod(bundle, putLongID, keyName, (jlong)value); |
| } |
| |
| template<> |
| void put(jstring keyName, const double& value) { |
| env->CallVoidMethod(bundle, putDoubleID, keyName, (jdouble)value); |
| } |
| |
| template<> |
| void put(jstring keyName, const std::string& value) { |
| env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value.c_str())); |
| } |
| |
| template<> |
| void put(jstring keyName, const std::pair<int64_t, int64_t>& value) { |
| ; // rate is currently ignored |
| } |
| |
| template<> |
| void put(jstring keyName, const std::monostate& value) { |
| ; // none is currently ignored |
| } |
| |
| // string char * helpers |
| |
| template<> |
| void put(jstring keyName, const char * const& value) { |
| env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value)); |
| } |
| |
| template<> |
| void put(jstring keyName, char * const& value) { |
| env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value)); |
| } |
| |
| // We allow both jstring and non-jstring variants. |
| template<typename T> |
| void put(const char *keyName, const T& value) { |
| put(env->NewStringUTF(keyName), value); |
| } |
| }; |
| } // namespace |
| |
| // place the attributes into a java PersistableBundle object |
| jobject MediaMetricsJNI::writeMetricsToBundle( |
| JNIEnv* env, mediametrics::Item *item, jobject bundle) |
| { |
| BundleHelper bh(env, bundle); |
| |
| if (bh.bundle == nullptr) { |
| ALOGE("%s: unable to create Bundle", __func__); |
| return nullptr; |
| } |
| |
| bh.put(mediametrics::BUNDLE_KEY, item->getKey().c_str()); |
| if (item->getPid() != -1) { |
| bh.put(mediametrics::BUNDLE_PID, (int32_t)item->getPid()); |
| } |
| if (item->getTimestamp() > 0) { |
| bh.put(mediametrics::BUNDLE_TIMESTAMP, (int64_t)item->getTimestamp()); |
| } |
| if (static_cast<int32_t>(item->getUid()) != -1) { |
| bh.put(mediametrics::BUNDLE_UID, (int32_t)item->getUid()); |
| } |
| for (const auto &prop : *item) { |
| const char *name = prop.getName(); |
| if (name == nullptr) continue; |
| prop.visit([&] (auto &value) { bh.put(name, value); }); |
| } |
| 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; |
| } |
| |
| return (jint)mediametrics::BaseItem::submitBuffer((char *)buffer, length); |
| } |
| |
| // Helper function to convert a native PersistableBundle to a Java |
| // PersistableBundle. |
| jobject MediaMetricsJNI::nativeToJavaPersistableBundle(JNIEnv *env, |
| os::PersistableBundle* nativeBundle) { |
| if (env == NULL || nativeBundle == NULL) { |
| ALOGE("Unexpected NULL parmeter"); |
| return NULL; |
| } |
| |
| // Create a Java parcel with the native parcel data. |
| // Then create a new PersistableBundle with that parcel as a parameter. |
| jobject jParcel = android::createJavaParcelObject(env); |
| if (jParcel == NULL) { |
| ALOGE("Failed to create a Java Parcel."); |
| return NULL; |
| } |
| |
| android::Parcel* nativeParcel = android::parcelForJavaObject(env, jParcel); |
| if (nativeParcel == NULL) { |
| ALOGE("Failed to get the native Parcel."); |
| return NULL; |
| } |
| |
| android::status_t result = nativeBundle->writeToParcel(nativeParcel); |
| nativeParcel->setDataPosition(0); |
| if (result != android::OK) { |
| ALOGE("Failed to write nativeBundle to Parcel: %d.", result); |
| return NULL; |
| } |
| |
| #define STATIC_INIT_JNI(T, obj, method, globalref, ...) \ |
| static T obj{};\ |
| if (obj == NULL) { \ |
| obj = method(__VA_ARGS__); \ |
| if (obj == NULL) { \ |
| ALOGE("%s can't find " #obj, __func__); \ |
| return NULL; \ |
| } else { \ |
| obj = globalref; \ |
| }\ |
| } \ |
| |
| STATIC_INIT_JNI(jclass, clazzBundle, env->FindClass, |
| static_cast<jclass>(env->NewGlobalRef(clazzBundle)), |
| "android/os/PersistableBundle"); |
| STATIC_INIT_JNI(jfieldID, bundleCreatorId, env->GetStaticFieldID, |
| bundleCreatorId, |
| clazzBundle, "CREATOR", "Landroid/os/Parcelable$Creator;"); |
| STATIC_INIT_JNI(jobject, bundleCreator, env->GetStaticObjectField, |
| env->NewGlobalRef(bundleCreator), |
| clazzBundle, bundleCreatorId); |
| STATIC_INIT_JNI(jclass, clazzCreator, env->FindClass, |
| static_cast<jclass>(env->NewGlobalRef(clazzCreator)), |
| "android/os/Parcelable$Creator"); |
| STATIC_INIT_JNI(jmethodID, createFromParcelId, env->GetMethodID, |
| createFromParcelId, |
| clazzCreator, "createFromParcel", "(Landroid/os/Parcel;)Ljava/lang/Object;"); |
| |
| jobject newBundle = env->CallObjectMethod(bundleCreator, createFromParcelId, jParcel); |
| if (newBundle == NULL) { |
| ALOGE("Failed to create a new PersistableBundle " |
| "from the createFromParcel call."); |
| } |
| |
| return newBundle; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| 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 |