/*
|
* 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
|