/*
* 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 "NewAvrcpTargetJni"
#include <base/bind.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);
// 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);
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;
}
// TODO (apanicke): Use a map here to store the callback in order to
// support multi-browsing
SetBrowsedPlayerCb set_browsed_player_cb;
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);
}
using map_entry = std::pair<std::string, GetFolderItemsCb>;
std::map<std::string, GetFolderItemsCb> get_folder_items_cb_map;
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);
}
std::map<RawAddress, ::bluetooth::avrcp::VolumeInterface::VolumeChangedCb>
volumeCallbackMap;
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