/* * Copyright (C) 2018 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 "AvrcpTargetJni" #include <base/bind.h> #include <base/callback.h> #include <map> #include <mutex> #include <shared_mutex> #include <vector> #include "android_runtime/AndroidRuntime.h" #include "avrcp.h" #include "com_android_bluetooth.h" #include "utils/Log.h" using namespace bluetooth::avrcp; namespace android { // Static Variables static MediaCallbacks* mServiceCallbacks; static ServiceInterface* sServiceInterface; static jobject mJavaInterface; static std::shared_timed_mutex interface_mutex; static std::shared_timed_mutex callbacks_mutex; // Forward Declarations static void sendMediaKeyEvent(int, KeyState); static std::string getCurrentMediaId(); static SongInfo getSongInfo(); static PlayStatus getCurrentPlayStatus(); static std::vector<SongInfo> getNowPlayingList(); static uint16_t getCurrentPlayerId(); static std::vector<MediaPlayerInfo> getMediaPlayerList(); using SetBrowsedPlayerCb = MediaInterface::SetBrowsedPlayerCallback; static void setBrowsedPlayer(uint16_t player_id, SetBrowsedPlayerCb); using GetFolderItemsCb = MediaInterface::FolderItemsCallback; static void getFolderItems(uint16_t player_id, std::string media_id, GetFolderItemsCb cb); static void playItem(uint16_t player_id, bool now_playing, std::string media_id); static void setActiveDevice(const RawAddress& address); static void volumeDeviceConnected(const RawAddress& address); static void volumeDeviceConnected( const RawAddress& address, ::bluetooth::avrcp::VolumeInterface::VolumeChangedCb cb); static void volumeDeviceDisconnected(const RawAddress& address); static void setVolume(int8_t volume); // Local Variables // TODO (apanicke): Use a map here to store the callback in order to // support multi-browsing SetBrowsedPlayerCb set_browsed_player_cb; using map_entry = std::pair<std::string, GetFolderItemsCb>; std::map<std::string, GetFolderItemsCb> get_folder_items_cb_map; std::map<RawAddress, ::bluetooth::avrcp::VolumeInterface::VolumeChangedCb> volumeCallbackMap; // TODO (apanicke): In the future, this interface should guarantee that // all calls happen on the JNI Thread. Right now this is very difficult // as it is hard to get a handle on the JNI thread from here. class AvrcpMediaInterfaceImpl : public MediaInterface { public: void SendKeyEvent(uint8_t key, KeyState state) { sendMediaKeyEvent(key, state); } void GetSongInfo(SongInfoCallback cb) override { auto info = getSongInfo(); cb.Run(info); } void GetPlayStatus(PlayStatusCallback cb) override { auto status = getCurrentPlayStatus(); cb.Run(status); } void GetNowPlayingList(NowPlayingCallback cb) override { auto curr_song_id = getCurrentMediaId(); auto now_playing_list = getNowPlayingList(); cb.Run(curr_song_id, std::move(now_playing_list)); } void GetMediaPlayerList(MediaListCallback cb) override { uint16_t current_player = getCurrentPlayerId(); auto player_list = getMediaPlayerList(); cb.Run(current_player, std::move(player_list)); } void GetFolderItems(uint16_t player_id, std::string media_id, FolderItemsCallback folder_cb) override { getFolderItems(player_id, media_id, folder_cb); } void SetBrowsedPlayer(uint16_t player_id, SetBrowsedPlayerCallback browse_cb) override { setBrowsedPlayer(player_id, browse_cb); } void RegisterUpdateCallback(MediaCallbacks* callback) override { // TODO (apanicke): Allow multiple registrations in the future mServiceCallbacks = callback; } void UnregisterUpdateCallback(MediaCallbacks* callback) override { mServiceCallbacks = nullptr; } void PlayItem(uint16_t player_id, bool now_playing, std::string media_id) override { playItem(player_id, now_playing, media_id); } void SetActiveDevice(const RawAddress& address) override { setActiveDevice(address); } }; static AvrcpMediaInterfaceImpl mAvrcpInterface; class VolumeInterfaceImpl : public VolumeInterface { public: void DeviceConnected(const RawAddress& bdaddr) override { volumeDeviceConnected(bdaddr); } void DeviceConnected(const RawAddress& bdaddr, VolumeChangedCb cb) override { volumeDeviceConnected(bdaddr, cb); } void DeviceDisconnected(const RawAddress& bdaddr) override { volumeDeviceDisconnected(bdaddr); } void SetVolume(int8_t volume) override { setVolume(volume); } }; static VolumeInterfaceImpl mVolumeInterface; static jmethodID method_getCurrentSongInfo; static jmethodID method_getPlaybackStatus; static jmethodID method_sendMediaKeyEvent; static jmethodID method_getCurrentMediaId; static jmethodID method_getNowPlayingList; static jmethodID method_setBrowsedPlayer; static jmethodID method_getCurrentPlayerId; static jmethodID method_getMediaPlayerList; static jmethodID method_getFolderItemsRequest; static jmethodID method_playItem; static jmethodID method_setActiveDevice; static jmethodID method_volumeDeviceConnected; static jmethodID method_volumeDeviceDisconnected; static jmethodID method_setVolume; static void classInitNative(JNIEnv* env, jclass clazz) { method_getCurrentSongInfo = env->GetMethodID( clazz, "getCurrentSongInfo", "()Lcom/android/bluetooth/avrcp/Metadata;"); method_getPlaybackStatus = env->GetMethodID( clazz, "getPlayStatus", "()Lcom/android/bluetooth/avrcp/PlayStatus;"); method_sendMediaKeyEvent = env->GetMethodID(clazz, "sendMediaKeyEvent", "(IZ)V"); method_getCurrentMediaId = env->GetMethodID(clazz, "getCurrentMediaId", "()Ljava/lang/String;"); method_getNowPlayingList = env->GetMethodID(clazz, "getNowPlayingList", "()Ljava/util/List;"); method_getCurrentPlayerId = env->GetMethodID(clazz, "getCurrentPlayerId", "()I"); method_getMediaPlayerList = env->GetMethodID(clazz, "getMediaPlayerList", "()Ljava/util/List;"); method_setBrowsedPlayer = env->GetMethodID(clazz, "setBrowsedPlayer", "(I)V"); method_getFolderItemsRequest = env->GetMethodID( clazz, "getFolderItemsRequest", "(ILjava/lang/String;)V"); method_playItem = env->GetMethodID(clazz, "playItem", "(IZLjava/lang/String;)V"); method_setActiveDevice = env->GetMethodID(clazz, "setActiveDevice", "(Ljava/lang/String;)V"); // Volume Management functions method_volumeDeviceConnected = env->GetMethodID(clazz, "deviceConnected", "(Ljava/lang/String;Z)V"); method_volumeDeviceDisconnected = env->GetMethodID(clazz, "deviceDisconnected", "(Ljava/lang/String;)V"); method_setVolume = env->GetMethodID(clazz, "setVolume", "(I)V"); ALOGI("%s: AvrcpTargetJni initialized!", __func__); } static void initNative(JNIEnv* env, jobject object) { ALOGD("%s", __func__); std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex); std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex); mJavaInterface = env->NewGlobalRef(object); sServiceInterface = getBluetoothInterface()->get_avrcp_service(); sServiceInterface->Init(&mAvrcpInterface, &mVolumeInterface); } static void sendMediaUpdateNative(JNIEnv* env, jobject object, jboolean metadata, jboolean state, jboolean queue) { ALOGD("%s", __func__); std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex); if (mServiceCallbacks == nullptr) { ALOGW("%s: Service not loaded.", __func__); return; } mServiceCallbacks->SendMediaUpdate(metadata == JNI_TRUE, state == JNI_TRUE, queue == JNI_TRUE); } static void sendFolderUpdateNative(JNIEnv* env, jobject object, jboolean available_players, jboolean addressed_player, jboolean uids) { ALOGD("%s", __func__); std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex); if (mServiceCallbacks == nullptr) { ALOGW("%s: Service not loaded.", __func__); return; } mServiceCallbacks->SendFolderUpdate(available_players == JNI_TRUE, addressed_player == JNI_TRUE, uids == JNI_TRUE); } static void cleanupNative(JNIEnv* env, jobject object) { std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex); std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex); get_folder_items_cb_map.clear(); volumeCallbackMap.clear(); sServiceInterface->Cleanup(); env->DeleteGlobalRef(mJavaInterface); mJavaInterface = nullptr; mServiceCallbacks = nullptr; sServiceInterface = nullptr; } jboolean connectDeviceNative(JNIEnv* env, jobject object, jstring address) { ALOGD("%s", __func__); std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex); if (mServiceCallbacks == nullptr) { ALOGW("%s: Service not loaded.", __func__); return JNI_FALSE; } const char* tmp_addr = env->GetStringUTFChars(address, 0); RawAddress bdaddr; bool success = RawAddress::FromString(tmp_addr, bdaddr); env->ReleaseStringUTFChars(address, tmp_addr); if (!success) return JNI_FALSE; return sServiceInterface->ConnectDevice(bdaddr) == true ? JNI_TRUE : JNI_FALSE; } jboolean disconnectDeviceNative(JNIEnv* env, jobject object, jstring address) { ALOGD("%s", __func__); std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex); if (mServiceCallbacks == nullptr) { ALOGW("%s: Service not loaded.", __func__); return JNI_FALSE; } const char* tmp_addr = env->GetStringUTFChars(address, 0); RawAddress bdaddr; bool success = RawAddress::FromString(tmp_addr, bdaddr); env->ReleaseStringUTFChars(address, tmp_addr); if (!success) return JNI_FALSE; return sServiceInterface->DisconnectDevice(bdaddr) == true ? JNI_TRUE : JNI_FALSE; } static void sendMediaKeyEvent(int key, KeyState state) { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return; sCallbackEnv->CallVoidMethod( mJavaInterface, method_sendMediaKeyEvent, key, state == KeyState::PUSHED ? JNI_TRUE : JNI_FALSE); } static SongInfo getSongInfoFromJavaObj(JNIEnv* env, jobject metadata) { SongInfo info; if (metadata == nullptr) return info; jclass class_metadata = env->GetObjectClass(metadata); jfieldID field_mediaId = env->GetFieldID(class_metadata, "mediaId", "Ljava/lang/String;"); jfieldID field_title = env->GetFieldID(class_metadata, "title", "Ljava/lang/String;"); jfieldID field_artist = env->GetFieldID(class_metadata, "artist", "Ljava/lang/String;"); jfieldID field_album = env->GetFieldID(class_metadata, "album", "Ljava/lang/String;"); jfieldID field_trackNum = env->GetFieldID(class_metadata, "trackNum", "Ljava/lang/String;"); jfieldID field_numTracks = env->GetFieldID(class_metadata, "numTracks", "Ljava/lang/String;"); jfieldID field_genre = env->GetFieldID(class_metadata, "genre", "Ljava/lang/String;"); jfieldID field_playingTime = env->GetFieldID(class_metadata, "duration", "Ljava/lang/String;"); jstring jstr = (jstring)env->GetObjectField(metadata, field_mediaId); if (jstr != nullptr) { const char* value = env->GetStringUTFChars(jstr, nullptr); info.media_id = std::string(value); env->ReleaseStringUTFChars(jstr, value); env->DeleteLocalRef(jstr); } jstr = (jstring)env->GetObjectField(metadata, field_title); if (jstr != nullptr) { const char* value = env->GetStringUTFChars(jstr, nullptr); info.attributes.insert( AttributeEntry(Attribute::TITLE, std::string(value))); env->ReleaseStringUTFChars(jstr, value); env->DeleteLocalRef(jstr); } jstr = (jstring)env->GetObjectField(metadata, field_artist); if (jstr != nullptr) { const char* value = env->GetStringUTFChars(jstr, nullptr); info.attributes.insert( AttributeEntry(Attribute::ARTIST_NAME, std::string(value))); env->ReleaseStringUTFChars(jstr, value); env->DeleteLocalRef(jstr); } jstr = (jstring)env->GetObjectField(metadata, field_album); if (jstr != nullptr) { const char* value = env->GetStringUTFChars(jstr, nullptr); info.attributes.insert( AttributeEntry(Attribute::ALBUM_NAME, std::string(value))); env->ReleaseStringUTFChars(jstr, value); env->DeleteLocalRef(jstr); } jstr = (jstring)env->GetObjectField(metadata, field_trackNum); if (jstr != nullptr) { const char* value = env->GetStringUTFChars(jstr, nullptr); info.attributes.insert( AttributeEntry(Attribute::TRACK_NUMBER, std::string(value))); env->ReleaseStringUTFChars(jstr, value); env->DeleteLocalRef(jstr); } jstr = (jstring)env->GetObjectField(metadata, field_numTracks); if (jstr != nullptr) { const char* value = env->GetStringUTFChars(jstr, nullptr); info.attributes.insert( AttributeEntry(Attribute::TOTAL_NUMBER_OF_TRACKS, std::string(value))); env->ReleaseStringUTFChars(jstr, value); env->DeleteLocalRef(jstr); } jstr = (jstring)env->GetObjectField(metadata, field_genre); if (jstr != nullptr) { const char* value = env->GetStringUTFChars(jstr, nullptr); info.attributes.insert( AttributeEntry(Attribute::GENRE, std::string(value))); env->ReleaseStringUTFChars(jstr, value); env->DeleteLocalRef(jstr); } jstr = (jstring)env->GetObjectField(metadata, field_playingTime); if (jstr != nullptr) { const char* value = env->GetStringUTFChars(jstr, nullptr); info.attributes.insert( AttributeEntry(Attribute::PLAYING_TIME, std::string(value))); env->ReleaseStringUTFChars(jstr, value); env->DeleteLocalRef(jstr); } return info; } static FolderInfo getFolderInfoFromJavaObj(JNIEnv* env, jobject folder) { FolderInfo info; jclass class_folder = env->GetObjectClass(folder); jfieldID field_mediaId = env->GetFieldID(class_folder, "mediaId", "Ljava/lang/String;"); jfieldID field_isPlayable = env->GetFieldID(class_folder, "isPlayable", "Z"); jfieldID field_name = env->GetFieldID(class_folder, "title", "Ljava/lang/String;"); jstring jstr = (jstring)env->GetObjectField(folder, field_mediaId); if (jstr != nullptr) { const char* value = env->GetStringUTFChars(jstr, nullptr); info.media_id = std::string(value); env->ReleaseStringUTFChars(jstr, value); } info.is_playable = env->GetBooleanField(folder, field_isPlayable) == JNI_TRUE; jstr = (jstring)env->GetObjectField(folder, field_name); if (jstr != nullptr) { const char* value = env->GetStringUTFChars(jstr, nullptr); info.name = std::string(value); env->ReleaseStringUTFChars(jstr, value); } return info; } static SongInfo getSongInfo() { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return SongInfo(); jobject metadata = sCallbackEnv->CallObjectMethod(mJavaInterface, method_getCurrentSongInfo); return getSongInfoFromJavaObj(sCallbackEnv.get(), metadata); } static PlayStatus getCurrentPlayStatus() { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return PlayStatus(); PlayStatus status; jobject playStatus = sCallbackEnv->CallObjectMethod(mJavaInterface, method_getPlaybackStatus); if (playStatus == nullptr) { ALOGE("%s: Got a null play status", __func__); return status; } jclass class_playStatus = sCallbackEnv->GetObjectClass(playStatus); jfieldID field_position = sCallbackEnv->GetFieldID(class_playStatus, "position", "J"); jfieldID field_duration = sCallbackEnv->GetFieldID(class_playStatus, "duration", "J"); jfieldID field_state = sCallbackEnv->GetFieldID(class_playStatus, "state", "B"); status.position = sCallbackEnv->GetLongField(playStatus, field_position); status.duration = sCallbackEnv->GetLongField(playStatus, field_duration); status.state = (PlayState)sCallbackEnv->GetByteField(playStatus, field_state); return status; } static std::string getCurrentMediaId() { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return ""; jstring media_id = (jstring)sCallbackEnv->CallObjectMethod( mJavaInterface, method_getCurrentMediaId); if (media_id == nullptr) { ALOGE("%s: Got a null media ID", __func__); return ""; } const char* value = sCallbackEnv->GetStringUTFChars(media_id, nullptr); std::string ret(value); sCallbackEnv->ReleaseStringUTFChars(media_id, value); return ret; } static std::vector<SongInfo> getNowPlayingList() { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return std::vector<SongInfo>(); jobject song_list = sCallbackEnv->CallObjectMethod(mJavaInterface, method_getNowPlayingList); if (song_list == nullptr) { ALOGE("%s: Got a null now playing list", __func__); return std::vector<SongInfo>(); } jclass class_list = sCallbackEnv->GetObjectClass(song_list); jmethodID method_get = sCallbackEnv->GetMethodID(class_list, "get", "(I)Ljava/lang/Object;"); jmethodID method_size = sCallbackEnv->GetMethodID(class_list, "size", "()I"); auto size = sCallbackEnv->CallIntMethod(song_list, method_size); if (size == 0) return std::vector<SongInfo>(); std::vector<SongInfo> ret; for (int i = 0; i < size; i++) { jobject song = sCallbackEnv->CallObjectMethod(song_list, method_get, i); ret.push_back(getSongInfoFromJavaObj(sCallbackEnv.get(), song)); sCallbackEnv->DeleteLocalRef(song); } return ret; } static uint16_t getCurrentPlayerId() { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return 0u; jint id = sCallbackEnv->CallIntMethod(mJavaInterface, method_getCurrentPlayerId); return (static_cast<int>(id) & 0xFFFF); } static std::vector<MediaPlayerInfo> getMediaPlayerList() { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return std::vector<MediaPlayerInfo>(); jobject player_list = (jobject)sCallbackEnv->CallObjectMethod( mJavaInterface, method_getMediaPlayerList); if (player_list == nullptr) { ALOGE("%s: Got a null media player list", __func__); return std::vector<MediaPlayerInfo>(); } jclass class_list = sCallbackEnv->GetObjectClass(player_list); jmethodID method_get = sCallbackEnv->GetMethodID(class_list, "get", "(I)Ljava/lang/Object;"); jmethodID method_size = sCallbackEnv->GetMethodID(class_list, "size", "()I"); jint list_size = sCallbackEnv->CallIntMethod(player_list, method_size); if (list_size == 0) { return std::vector<MediaPlayerInfo>(); } jclass class_playerInfo = sCallbackEnv->GetObjectClass( sCallbackEnv->CallObjectMethod(player_list, method_get, 0)); jfieldID field_playerId = sCallbackEnv->GetFieldID(class_playerInfo, "id", "I"); jfieldID field_name = sCallbackEnv->GetFieldID(class_playerInfo, "name", "Ljava/lang/String;"); jfieldID field_browsable = sCallbackEnv->GetFieldID(class_playerInfo, "browsable", "Z"); std::vector<MediaPlayerInfo> ret_list; for (jsize i = 0; i < list_size; i++) { jobject player = sCallbackEnv->CallObjectMethod(player_list, method_get, i); MediaPlayerInfo temp; temp.id = sCallbackEnv->GetIntField(player, field_playerId); jstring jstr = (jstring)sCallbackEnv->GetObjectField(player, field_name); if (jstr != nullptr) { const char* value = sCallbackEnv->GetStringUTFChars(jstr, nullptr); temp.name = std::string(value); sCallbackEnv->ReleaseStringUTFChars(jstr, value); sCallbackEnv->DeleteLocalRef(jstr); } temp.browsing_supported = sCallbackEnv->GetBooleanField(player, field_browsable) == JNI_TRUE ? true : false; ret_list.push_back(std::move(temp)); sCallbackEnv->DeleteLocalRef(player); } return ret_list; } static void setBrowsedPlayer(uint16_t player_id, SetBrowsedPlayerCb cb) { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return; set_browsed_player_cb = cb; sCallbackEnv->CallVoidMethod(mJavaInterface, method_setBrowsedPlayer, player_id); } static void setBrowsedPlayerResponseNative(JNIEnv* env, jobject object, jint player_id, jboolean success, jstring root_id, jint num_items) { ALOGD("%s", __func__); std::string root; if (root_id != nullptr) { const char* value = env->GetStringUTFChars(root_id, nullptr); root = std::string(value); env->ReleaseStringUTFChars(root_id, value); } set_browsed_player_cb.Run(success == JNI_TRUE, root, num_items); } static void getFolderItemsResponseNative(JNIEnv* env, jobject object, jstring parent_id, jobject list) { ALOGD("%s", __func__); std::string id; if (parent_id != nullptr) { const char* value = env->GetStringUTFChars(parent_id, nullptr); id = std::string(value); env->ReleaseStringUTFChars(parent_id, value); } // TODO (apanicke): Right now browsing will fail on a second device if two // devices browse the same folder. Use a MultiMap to fix this behavior so // that both callbacks can be handled with one lookup if a request comes // for a folder that is already trying to be looked at. if (get_folder_items_cb_map.find(id) == get_folder_items_cb_map.end()) { ALOGE("Could not find response callback for the request of \"%s\"", id.c_str()); return; } auto callback = get_folder_items_cb_map.find(id)->second; get_folder_items_cb_map.erase(id); if (list == nullptr) { ALOGE("%s: Got a null get folder items response list", __func__); callback.Run(std::vector<ListItem>()); return; } jclass class_list = env->GetObjectClass(list); jmethodID method_get = env->GetMethodID(class_list, "get", "(I)Ljava/lang/Object;"); jmethodID method_size = env->GetMethodID(class_list, "size", "()I"); jint list_size = env->CallIntMethod(list, method_size); if (list_size == 0) { callback.Run(std::vector<ListItem>()); return; } jclass class_listItem = env->GetObjectClass(env->CallObjectMethod(list, method_get, 0)); jfieldID field_isFolder = env->GetFieldID(class_listItem, "isFolder", "Z"); jfieldID field_folder = env->GetFieldID( class_listItem, "folder", "Lcom/android/bluetooth/avrcp/Folder;"); jfieldID field_song = env->GetFieldID( class_listItem, "song", "Lcom/android/bluetooth/avrcp/Metadata;"); std::vector<ListItem> ret_list; for (jsize i = 0; i < list_size; i++) { jobject item = env->CallObjectMethod(list, method_get, i); bool is_folder = env->GetBooleanField(item, field_isFolder) == JNI_TRUE; if (is_folder) { ListItem temp = {ListItem::FOLDER, getFolderInfoFromJavaObj( env, env->GetObjectField(item, field_folder)), SongInfo()}; ret_list.push_back(temp); } else { ListItem temp = { ListItem::SONG, FolderInfo(), getSongInfoFromJavaObj(env, env->GetObjectField(item, field_song))}; ret_list.push_back(temp); } env->DeleteLocalRef(item); } callback.Run(std::move(ret_list)); } static void getFolderItems(uint16_t player_id, std::string media_id, GetFolderItemsCb cb) { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return; // TODO (apanicke): Fix a potential media_id collision if two media players // use the same media_id scheme or two devices browse the same content. get_folder_items_cb_map.insert(map_entry(media_id, cb)); jstring j_media_id = sCallbackEnv->NewStringUTF(media_id.c_str()); sCallbackEnv->CallVoidMethod(mJavaInterface, method_getFolderItemsRequest, player_id, j_media_id); } static void playItem(uint16_t player_id, bool now_playing, std::string media_id) { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return; jstring j_media_id = sCallbackEnv->NewStringUTF(media_id.c_str()); sCallbackEnv->CallVoidMethod(mJavaInterface, method_playItem, player_id, now_playing ? JNI_TRUE : JNI_FALSE, j_media_id); } static void setActiveDevice(const RawAddress& address) { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return; jstring j_bdaddr = sCallbackEnv->NewStringUTF(address.ToString().c_str()); sCallbackEnv->CallVoidMethod(mJavaInterface, method_setActiveDevice, j_bdaddr); } static void volumeDeviceConnected(const RawAddress& address) { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return; jstring j_bdaddr = sCallbackEnv->NewStringUTF(address.ToString().c_str()); sCallbackEnv->CallVoidMethod(mJavaInterface, method_volumeDeviceConnected, j_bdaddr, JNI_FALSE); } static void volumeDeviceConnected( const RawAddress& address, ::bluetooth::avrcp::VolumeInterface::VolumeChangedCb cb) { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return; volumeCallbackMap.emplace(address, cb); jstring j_bdaddr = sCallbackEnv->NewStringUTF(address.ToString().c_str()); sCallbackEnv->CallVoidMethod(mJavaInterface, method_volumeDeviceConnected, j_bdaddr, JNI_TRUE); } static void volumeDeviceDisconnected(const RawAddress& address) { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return; volumeCallbackMap.erase(address); jstring j_bdaddr = sCallbackEnv->NewStringUTF(address.ToString().c_str()); sCallbackEnv->CallVoidMethod(mJavaInterface, method_volumeDeviceDisconnected, j_bdaddr); } static void sendVolumeChangedNative(JNIEnv* env, jobject object, jint volume) { ALOGD("%s", __func__); for (const auto& cb : volumeCallbackMap) { cb.second.Run(volume & 0x7F); } } static void setVolume(int8_t volume) { ALOGD("%s", __func__); std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mJavaInterface) return; sCallbackEnv->CallVoidMethod(mJavaInterface, method_setVolume, volume); } static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void*)classInitNative}, {"initNative", "()V", (void*)initNative}, {"sendMediaUpdateNative", "(ZZZ)V", (void*)sendMediaUpdateNative}, {"sendFolderUpdateNative", "(ZZZ)V", (void*)sendFolderUpdateNative}, {"setBrowsedPlayerResponseNative", "(IZLjava/lang/String;I)V", (void*)setBrowsedPlayerResponseNative}, {"getFolderItemsResponseNative", "(Ljava/lang/String;Ljava/util/List;)V", (void*)getFolderItemsResponseNative}, {"cleanupNative", "()V", (void*)cleanupNative}, {"connectDeviceNative", "(Ljava/lang/String;)Z", (void*)connectDeviceNative}, {"disconnectDeviceNative", "(Ljava/lang/String;)Z", (void*)disconnectDeviceNative}, {"sendVolumeChangedNative", "(I)V", (void*)sendVolumeChangedNative}, }; int register_com_android_bluetooth_avrcp_target(JNIEnv* env) { return jniRegisterNativeMethods( env, "com/android/bluetooth/avrcp/AvrcpNativeInterface", sMethods, NELEM(sMethods)); } } // namespace android