blob: 2d9e87bcbabb84024dea76142721aa7a4081a57f [file] [log] [blame]
/*
* Copyright (C) 2016 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 "BluetoothAvrcpControllerJni"
#define LOG_NDEBUG 0
#include "android_runtime/AndroidRuntime.h"
#include "com_android_bluetooth.h"
#include "hardware/bt_rc.h"
#include "utils/Log.h"
#include <string.h>
#include <shared_mutex>
namespace android {
static jmethodID method_handlePassthroughRsp;
static jmethodID method_onConnectionStateChanged;
static jmethodID method_getRcFeatures;
static jmethodID method_setplayerappsettingrsp;
static jmethodID method_handleplayerappsetting;
static jmethodID method_handleplayerappsettingchanged;
static jmethodID method_handleSetAbsVolume;
static jmethodID method_handleRegisterNotificationAbsVol;
static jmethodID method_handletrackchanged;
static jmethodID method_handleplaypositionchanged;
static jmethodID method_handleplaystatuschanged;
static jmethodID method_handleGetFolderItemsRsp;
static jmethodID method_handleGetPlayerItemsRsp;
static jmethodID method_handleGroupNavigationRsp;
static jmethodID method_createFromNativeMediaItem;
static jmethodID method_createFromNativeFolderItem;
static jmethodID method_createFromNativePlayerItem;
static jmethodID method_handleChangeFolderRsp;
static jmethodID method_handleSetBrowsedPlayerRsp;
static jmethodID method_handleSetAddressedPlayerRsp;
static jmethodID method_handleAddressedPlayerChanged;
static jmethodID method_handleNowPlayingContentChanged;
static jclass class_MediaBrowser_MediaItem;
static jclass class_AvrcpPlayer;
static const btrc_ctrl_interface_t* sBluetoothAvrcpInterface = NULL;
static jobject sCallbacksObj = NULL;
static std::shared_timed_mutex sCallbacks_mutex;
static void btavrcp_passthrough_response_callback(const RawAddress& bd_addr,
int id, int pressed) {
ALOGI("%s: id: %d, pressed: %d", __func__, id, pressed);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handlePassthroughRsp,
(jint)id, (jint)pressed, addr.get());
}
static void btavrcp_groupnavigation_response_callback(int id, int pressed) {
ALOGV("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGroupNavigationRsp,
(jint)id, (jint)pressed);
}
static void btavrcp_connection_state_callback(bool rc_connect, bool br_connect,
const RawAddress& bd_addr) {
ALOGI("%s: conn state: rc: %d br: %d", __func__, rc_connect, br_connect);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_onConnectionStateChanged,
(jboolean)rc_connect, (jboolean)br_connect,
addr.get());
}
static void btavrcp_get_rcfeatures_callback(const RawAddress& bd_addr,
int features) {
ALOGV("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_getRcFeatures, addr.get(),
(jint)features);
}
static void btavrcp_setplayerapplicationsetting_rsp_callback(
const RawAddress& bd_addr, uint8_t accepted) {
ALOGV("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_setplayerappsettingrsp,
addr.get(), (jint)accepted);
}
static void btavrcp_playerapplicationsetting_callback(
const RawAddress& bd_addr, uint8_t num_attr,
btrc_player_app_attr_t* app_attrs, uint8_t num_ext_attr,
btrc_player_app_ext_attr_t* ext_attrs) {
ALOGI("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
/* TODO ext attrs
* Flattening defined attributes: <id,num_values,values[]>
*/
jint arraylen = 0;
for (int i = 0; i < num_attr; i++) {
/*2 bytes for id and num */
arraylen += 2 + app_attrs[i].num_val;
}
ALOGV(" arraylen %d", arraylen);
ScopedLocalRef<jbyteArray> playerattribs(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(arraylen));
if (!playerattribs.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
for (int i = 0, k = 0; (i < num_attr) && (k < arraylen); i++) {
sCallbackEnv->SetByteArrayRegion(playerattribs.get(), k, 1,
(jbyte*)&(app_attrs[i].attr_id));
k++;
sCallbackEnv->SetByteArrayRegion(playerattribs.get(), k, 1,
(jbyte*)&(app_attrs[i].num_val));
k++;
sCallbackEnv->SetByteArrayRegion(playerattribs.get(), k,
app_attrs[i].num_val,
(jbyte*)(app_attrs[i].attr_val));
k = k + app_attrs[i].num_val;
}
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplayerappsetting,
addr.get(), playerattribs.get(), (jint)arraylen);
}
static void btavrcp_playerapplicationsetting_changed_callback(
const RawAddress& bd_addr, const btrc_player_settings_t& vals) {
ALOGI("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
int arraylen = vals.num_attr * 2;
ScopedLocalRef<jbyteArray> playerattribs(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(arraylen));
if (!playerattribs.get()) {
ALOGE("Fail to new jbyteArray playerattribs ");
return;
}
/*
* Flatening format: <id,val>
*/
for (int i = 0, k = 0; (i < vals.num_attr) && (k < arraylen); i++) {
sCallbackEnv->SetByteArrayRegion(playerattribs.get(), k, 1,
(jbyte*)&(vals.attr_ids[i]));
k++;
sCallbackEnv->SetByteArrayRegion(playerattribs.get(), k, 1,
(jbyte*)&(vals.attr_values[i]));
k++;
}
sCallbackEnv->CallVoidMethod(sCallbacksObj,
method_handleplayerappsettingchanged, addr.get(),
playerattribs.get(), (jint)arraylen);
}
static void btavrcp_set_abs_vol_cmd_callback(const RawAddress& bd_addr,
uint8_t abs_vol, uint8_t label) {
ALOGI("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleSetAbsVolume,
addr.get(), (jbyte)abs_vol, (jbyte)label);
}
static void btavrcp_register_notification_absvol_callback(
const RawAddress& bd_addr, uint8_t label) {
ALOGI("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj,
method_handleRegisterNotificationAbsVol,
addr.get(), (jbyte)label);
}
static void btavrcp_track_changed_callback(const RawAddress& bd_addr,
uint8_t num_attr,
btrc_element_attr_val_t* p_attrs) {
/*
* byteArray will be formatted like this: id,len,string
* Assuming text feild to be null terminated.
*/
ALOGI("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
ScopedLocalRef<jintArray> attribIds(sCallbackEnv.get(),
sCallbackEnv->NewIntArray(num_attr));
if (!attribIds.get()) {
ALOGE(" failed to set new array for attribIds");
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
jclass strclazz = sCallbackEnv->FindClass("java/lang/String");
ScopedLocalRef<jobjectArray> stringArray(
sCallbackEnv.get(),
sCallbackEnv->NewObjectArray((jint)num_attr, strclazz, 0));
if (!stringArray.get()) {
ALOGE(" failed to get String array");
return;
}
for (jint i = 0; i < num_attr; i++) {
ScopedLocalRef<jstring> str(
sCallbackEnv.get(),
sCallbackEnv->NewStringUTF((char*)(p_attrs[i].text)));
if (!str.get()) {
ALOGE("Unable to get str");
return;
}
sCallbackEnv->SetIntArrayRegion(attribIds.get(), i, 1,
(jint*)&(p_attrs[i].attr_id));
sCallbackEnv->SetObjectArrayElement(stringArray.get(), i, str.get());
}
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handletrackchanged,
addr.get(), (jbyte)(num_attr), attribIds.get(),
stringArray.get());
}
static void btavrcp_play_position_changed_callback(const RawAddress& bd_addr,
uint32_t song_len,
uint32_t song_pos) {
ALOGI("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplaypositionchanged,
addr.get(), (jint)(song_len), (jint)song_pos);
}
static void btavrcp_play_status_changed_callback(
const RawAddress& bd_addr, btrc_play_status_t play_status) {
ALOGI("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplaystatuschanged,
addr.get(), (jbyte)play_status);
}
static void btavrcp_get_folder_items_callback(
const RawAddress& bd_addr, btrc_status_t status,
const btrc_folder_items_t* folder_items, uint8_t count) {
/* Folder items are list of items that can be either BTRC_ITEM_PLAYER
* BTRC_ITEM_MEDIA, BTRC_ITEM_FOLDER. Here we translate them to their java
* counterparts by calling the java constructor for each of the items.
*/
ALOGV("%s count %d", __func__, count);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
// Inspect if the first element is a folder/item or player listing. They are
// always exclusive.
bool isPlayerListing =
count > 0 && (folder_items[0].item_type == BTRC_ITEM_PLAYER);
// Initialize arrays for Folder OR Player listing.
ScopedLocalRef<jobjectArray> itemArray(sCallbackEnv.get(), NULL);
if (isPlayerListing) {
itemArray.reset(
sCallbackEnv->NewObjectArray((jint)count, class_AvrcpPlayer, 0));
} else {
itemArray.reset(sCallbackEnv->NewObjectArray(
(jint)count, class_MediaBrowser_MediaItem, 0));
}
if (!itemArray.get()) {
ALOGE("%s itemArray allocation failed.", __func__);
return;
}
for (int i = 0; i < count; i++) {
const btrc_folder_items_t* item = &(folder_items[i]);
ALOGV("%s item type %d", __func__, item->item_type);
switch (item->item_type) {
case BTRC_ITEM_MEDIA: {
// Parse name
ScopedLocalRef<jstring> mediaName(
sCallbackEnv.get(),
sCallbackEnv->NewStringUTF((const char*)item->media.name));
if (!mediaName.get()) {
ALOGE("%s can't allocate media name string!", __func__);
return;
}
// Parse UID
long long uid = *(long long*)item->media.uid;
// Parse Attrs
ScopedLocalRef<jintArray> attrIdArray(
sCallbackEnv.get(),
sCallbackEnv->NewIntArray(item->media.num_attrs));
if (!attrIdArray.get()) {
ALOGE("%s can't allocate attr id array!", __func__);
return;
}
ScopedLocalRef<jobjectArray> attrValArray(
sCallbackEnv.get(),
sCallbackEnv->NewObjectArray(
item->media.num_attrs,
sCallbackEnv->FindClass("java/lang/String"), 0));
if (!attrValArray.get()) {
ALOGE("%s can't allocate attr val array!", __func__);
return;
}
for (int j = 0; j < item->media.num_attrs; j++) {
sCallbackEnv->SetIntArrayRegion(
attrIdArray.get(), j, 1,
(jint*)&(item->media.p_attrs[j].attr_id));
ScopedLocalRef<jstring> attrValStr(
sCallbackEnv.get(),
sCallbackEnv->NewStringUTF((char*)(item->media.p_attrs[j].text)));
sCallbackEnv->SetObjectArrayElement(attrValArray.get(), j,
attrValStr.get());
}
ScopedLocalRef<jobject> mediaObj(
sCallbackEnv.get(),
(jobject)sCallbackEnv->CallObjectMethod(
sCallbacksObj, method_createFromNativeMediaItem, uid,
(jint)item->media.type, mediaName.get(), attrIdArray.get(),
attrValArray.get()));
if (!mediaObj.get()) {
ALOGE("%s failed to creae MediaItem for type ITEM_MEDIA", __func__);
return;
}
sCallbackEnv->SetObjectArrayElement(itemArray.get(), i, mediaObj.get());
break;
}
case BTRC_ITEM_FOLDER: {
// Parse name
ScopedLocalRef<jstring> folderName(
sCallbackEnv.get(),
sCallbackEnv->NewStringUTF((const char*)item->folder.name));
if (!folderName.get()) {
ALOGE("%s can't allocate folder name string!", __func__);
return;
}
// Parse UID
long long uid = *(long long*)item->folder.uid;
ScopedLocalRef<jobject> folderObj(
sCallbackEnv.get(),
(jobject)sCallbackEnv->CallObjectMethod(
sCallbacksObj, method_createFromNativeFolderItem, uid,
(jint)item->folder.type, folderName.get(),
(jint)item->folder.playable));
if (!folderObj.get()) {
ALOGE("%s failed to create MediaItem for type ITEM_FOLDER", __func__);
return;
}
sCallbackEnv->SetObjectArrayElement(itemArray.get(), i,
folderObj.get());
break;
}
case BTRC_ITEM_PLAYER: {
// Parse name
isPlayerListing = true;
jint id = (jint)item->player.player_id;
jint playerType = (jint)item->player.major_type;
jint playStatus = (jint)item->player.play_status;
ScopedLocalRef<jbyteArray> featureBitArray(
sCallbackEnv.get(),
sCallbackEnv->NewByteArray(BTRC_FEATURE_BIT_MASK_SIZE *
sizeof(uint8_t)));
if (!featureBitArray.get()) {
ALOGE("%s failed to allocate featureBitArray", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(
featureBitArray.get(), 0,
sizeof(uint8_t) * BTRC_FEATURE_BIT_MASK_SIZE,
(jbyte*)item->player.features);
ScopedLocalRef<jstring> playerName(
sCallbackEnv.get(),
sCallbackEnv->NewStringUTF((const char*)item->player.name));
if (!playerName.get()) {
ALOGE("%s can't allocate player name string!", __func__);
return;
}
ScopedLocalRef<jobject> playerObj(
sCallbackEnv.get(),
(jobject)sCallbackEnv->CallObjectMethod(
sCallbacksObj, method_createFromNativePlayerItem, id,
playerName.get(), featureBitArray.get(), playStatus,
playerType));
if (!playerObj.get()) {
ALOGE("%s failed to create AvrcpPlayer from ITEM_PLAYER", __func__);
return;
}
sCallbackEnv->SetObjectArrayElement(itemArray.get(), i,
playerObj.get());
break;
}
default:
ALOGE("%s cannot understand type %d", __func__, item->item_type);
}
}
if (isPlayerListing) {
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetPlayerItemsRsp,
addr.get(), itemArray.get());
} else {
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetFolderItemsRsp,
addr.get(), status, itemArray.get());
}
}
static void btavrcp_change_path_callback(const RawAddress& bd_addr,
uint32_t count) {
ALOGI("%s count %d", __func__, count);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleChangeFolderRsp,
addr.get(), (jint)count);
}
static void btavrcp_set_browsed_player_callback(const RawAddress& bd_addr,
uint8_t num_items,
uint8_t depth) {
ALOGI("%s items %d depth %d", __func__, num_items, depth);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleSetBrowsedPlayerRsp,
addr.get(), (jint)num_items, (jint)depth);
}
static void btavrcp_set_addressed_player_callback(const RawAddress& bd_addr,
uint8_t status) {
ALOGI("%s status %d", __func__, status);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj,
method_handleSetAddressedPlayerRsp, addr.get(),
(jint)status);
}
static void btavrcp_addressed_player_changed_callback(const RawAddress& bd_addr,
uint16_t id) {
ALOGI("%s status %d", __func__, id);
std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!sCallbacksObj) {
ALOGE("%s: sCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(
sCallbacksObj, method_handleAddressedPlayerChanged, addr.get(), (jint)id);
}
static void btavrcp_now_playing_content_changed_callback(
const RawAddress& bd_addr) {
ALOGI("%s", __func__);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr.address);
sCallbackEnv->CallVoidMethod(
sCallbacksObj, method_handleNowPlayingContentChanged, addr.get());
}
static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
sizeof(sBluetoothAvrcpCallbacks),
btavrcp_passthrough_response_callback,
btavrcp_groupnavigation_response_callback,
btavrcp_connection_state_callback,
btavrcp_get_rcfeatures_callback,
btavrcp_setplayerapplicationsetting_rsp_callback,
btavrcp_playerapplicationsetting_callback,
btavrcp_playerapplicationsetting_changed_callback,
btavrcp_set_abs_vol_cmd_callback,
btavrcp_register_notification_absvol_callback,
btavrcp_track_changed_callback,
btavrcp_play_position_changed_callback,
btavrcp_play_status_changed_callback,
btavrcp_get_folder_items_callback,
btavrcp_change_path_callback,
btavrcp_set_browsed_player_callback,
btavrcp_set_addressed_player_callback,
btavrcp_addressed_player_changed_callback,
btavrcp_now_playing_content_changed_callback};
static void classInitNative(JNIEnv* env, jclass clazz) {
method_handlePassthroughRsp =
env->GetMethodID(clazz, "handlePassthroughRsp", "(II[B)V");
method_handleGroupNavigationRsp =
env->GetMethodID(clazz, "handleGroupNavigationRsp", "(II)V");
method_onConnectionStateChanged =
env->GetMethodID(clazz, "onConnectionStateChanged", "(ZZ[B)V");
method_getRcFeatures = env->GetMethodID(clazz, "getRcFeatures", "([BI)V");
method_setplayerappsettingrsp =
env->GetMethodID(clazz, "setPlayerAppSettingRsp", "([BB)V");
method_handleplayerappsetting =
env->GetMethodID(clazz, "handlePlayerAppSetting", "([B[BI)V");
method_handleplayerappsettingchanged =
env->GetMethodID(clazz, "onPlayerAppSettingChanged", "([B[BI)V");
method_handleSetAbsVolume =
env->GetMethodID(clazz, "handleSetAbsVolume", "([BBB)V");
method_handleRegisterNotificationAbsVol =
env->GetMethodID(clazz, "handleRegisterNotificationAbsVol", "([BB)V");
method_handletrackchanged =
env->GetMethodID(clazz, "onTrackChanged", "([BB[I[Ljava/lang/String;)V");
method_handleplaypositionchanged =
env->GetMethodID(clazz, "onPlayPositionChanged", "([BII)V");
method_handleplaystatuschanged =
env->GetMethodID(clazz, "onPlayStatusChanged", "([BB)V");
method_handleGetFolderItemsRsp =
env->GetMethodID(clazz, "handleGetFolderItemsRsp",
"([BI[Landroid/media/browse/MediaBrowser$MediaItem;)V");
method_handleGetPlayerItemsRsp = env->GetMethodID(
clazz, "handleGetPlayerItemsRsp",
"([B[Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V");
method_createFromNativeMediaItem =
env->GetMethodID(clazz, "createFromNativeMediaItem",
"(JILjava/lang/String;[I[Ljava/lang/String;)Landroid/"
"media/browse/MediaBrowser$MediaItem;");
method_createFromNativeFolderItem = env->GetMethodID(
clazz, "createFromNativeFolderItem",
"(JILjava/lang/String;I)Landroid/media/browse/MediaBrowser$MediaItem;");
method_createFromNativePlayerItem =
env->GetMethodID(clazz, "createFromNativePlayerItem",
"(ILjava/lang/String;[BII)Lcom/android/bluetooth/"
"avrcpcontroller/AvrcpPlayer;");
method_handleChangeFolderRsp =
env->GetMethodID(clazz, "handleChangeFolderRsp", "([BI)V");
method_handleSetBrowsedPlayerRsp =
env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "([BII)V");
method_handleSetAddressedPlayerRsp =
env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "([BI)V");
method_handleAddressedPlayerChanged =
env->GetMethodID(clazz, "handleAddressedPlayerChanged", "([BI)V");
method_handleNowPlayingContentChanged =
env->GetMethodID(clazz, "handleNowPlayingContentChanged", "([B)V");
ALOGI("%s: succeeds", __func__);
}
static void initNative(JNIEnv* env, jobject object) {
std::unique_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
jclass tmpMediaItem =
env->FindClass("android/media/browse/MediaBrowser$MediaItem");
class_MediaBrowser_MediaItem = (jclass)env->NewGlobalRef(tmpMediaItem);
jclass tmpBtPlayer =
env->FindClass("com/android/bluetooth/avrcpcontroller/AvrcpPlayer");
class_AvrcpPlayer = (jclass)env->NewGlobalRef(tmpBtPlayer);
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == NULL) {
ALOGE("Bluetooth module is not loaded");
return;
}
if (sBluetoothAvrcpInterface != NULL) {
ALOGW("Cleaning up Avrcp Interface before initializing...");
sBluetoothAvrcpInterface->cleanup();
sBluetoothAvrcpInterface = NULL;
}
if (sCallbacksObj != NULL) {
ALOGW("Cleaning up Avrcp callback object");
env->DeleteGlobalRef(sCallbacksObj);
sCallbacksObj = NULL;
}
sBluetoothAvrcpInterface =
(btrc_ctrl_interface_t*)btInf->get_profile_interface(
BT_PROFILE_AV_RC_CTRL_ID);
if (sBluetoothAvrcpInterface == NULL) {
ALOGE("Failed to get Bluetooth Avrcp Controller Interface");
return;
}
bt_status_t status =
sBluetoothAvrcpInterface->init(&sBluetoothAvrcpCallbacks);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed to initialize Bluetooth Avrcp Controller, status: %d",
status);
sBluetoothAvrcpInterface = NULL;
return;
}
sCallbacksObj = env->NewGlobalRef(object);
}
static void cleanupNative(JNIEnv* env, jobject object) {
std::unique_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == NULL) {
ALOGE("Bluetooth module is not loaded");
return;
}
if (sBluetoothAvrcpInterface != NULL) {
sBluetoothAvrcpInterface->cleanup();
sBluetoothAvrcpInterface = NULL;
}
if (sCallbacksObj != NULL) {
env->DeleteGlobalRef(sCallbacksObj);
sCallbacksObj = NULL;
}
}
static jboolean sendPassThroughCommandNative(JNIEnv* env, jobject object,
jbyteArray address, jint key_code,
jint key_state) {
if (!sBluetoothAvrcpInterface) return JNI_FALSE;
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
ALOGI("key_code: %d, key_state: %d", key_code, key_state);
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
bt_status_t status = sBluetoothAvrcpInterface->send_pass_through_cmd(
rawAddress, (uint8_t)key_code, (uint8_t)key_state);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending passthru command, status: %d", status);
}
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
static jboolean sendGroupNavigationCommandNative(JNIEnv* env, jobject object,
jbyteArray address,
jint key_code,
jint key_state) {
if (!sBluetoothAvrcpInterface) return JNI_FALSE;
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
ALOGI("key_code: %d, key_state: %d", key_code, key_state);
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
bt_status_t status = sBluetoothAvrcpInterface->send_group_navigation_cmd(
rawAddress, (uint8_t)key_code, (uint8_t)key_state);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending Grp Navigation command, status: %d", status);
}
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
static void setPlayerApplicationSettingValuesNative(JNIEnv* env, jobject object,
jbyteArray address,
jbyte num_attrib,
jbyteArray attrib_ids,
jbyteArray attrib_val) {
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return;
}
uint8_t* pAttrs = new uint8_t[num_attrib];
uint8_t* pAttrsVal = new uint8_t[num_attrib];
if ((!pAttrs) || (!pAttrsVal)) {
delete[] pAttrs;
ALOGE("setPlayerApplicationSettingValuesNative: not have enough memeory");
return;
}
jbyte* attr = env->GetByteArrayElements(attrib_ids, NULL);
jbyte* attr_val = env->GetByteArrayElements(attrib_val, NULL);
if ((!attr) || (!attr_val)) {
delete[] pAttrs;
delete[] pAttrsVal;
jniThrowIOException(env, EINVAL);
return;
}
int i;
for (i = 0; i < num_attrib; ++i) {
pAttrs[i] = (uint8_t)attr[i];
pAttrsVal[i] = (uint8_t)attr_val[i];
}
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
bt_status_t status = sBluetoothAvrcpInterface->set_player_app_setting_cmd(
rawAddress, (uint8_t)num_attrib, pAttrs, pAttrsVal);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending setPlAppSettValNative command, status: %d", status);
}
delete[] pAttrs;
delete[] pAttrsVal;
env->ReleaseByteArrayElements(attrib_ids, attr, 0);
env->ReleaseByteArrayElements(attrib_val, attr_val, 0);
env->ReleaseByteArrayElements(address, addr, 0);
}
static void sendAbsVolRspNative(JNIEnv* env, jobject object, jbyteArray address,
jint abs_vol, jint label) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return;
}
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
bt_status_t status = sBluetoothAvrcpInterface->set_volume_rsp(
rawAddress, (uint8_t)abs_vol, (uint8_t)label);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending sendAbsVolRspNative command, status: %d", status);
}
env->ReleaseByteArrayElements(address, addr, 0);
}
static void sendRegisterAbsVolRspNative(JNIEnv* env, jobject object,
jbyteArray address, jbyte rsp_type,
jint abs_vol, jint label) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return;
}
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
bt_status_t status = sBluetoothAvrcpInterface->register_abs_vol_rsp(
rawAddress, (btrc_notification_type_t)rsp_type, (uint8_t)abs_vol,
(uint8_t)label);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending sendRegisterAbsVolRspNative command, status: %d",
status);
}
env->ReleaseByteArrayElements(address, addr, 0);
}
static void getPlaybackStateNative(JNIEnv* env, jobject object,
jbyteArray address) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return;
}
ALOGV("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
bt_status_t status =
sBluetoothAvrcpInterface->get_playback_state_cmd(rawAddress);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending getPlaybackStateNative command, status: %d", status);
}
env->ReleaseByteArrayElements(address, addr, 0);
}
static void getNowPlayingListNative(JNIEnv* env, jobject object,
jbyteArray address, jint start, jint end) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return;
}
ALOGV("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
bt_status_t status = sBluetoothAvrcpInterface->get_now_playing_list_cmd(
rawAddress, start, end);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending getNowPlayingListNative command, status: %d", status);
}
env->ReleaseByteArrayElements(address, addr, 0);
}
static void getFolderListNative(JNIEnv* env, jobject object, jbyteArray address,
jint start, jint end) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return;
}
ALOGV("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
bt_status_t status =
sBluetoothAvrcpInterface->get_folder_list_cmd(rawAddress, start, end);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending getFolderListNative command, status: %d", status);
}
env->ReleaseByteArrayElements(address, addr, 0);
}
static void getPlayerListNative(JNIEnv* env, jobject object, jbyteArray address,
jint start, jint end) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return;
}
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
bt_status_t status =
sBluetoothAvrcpInterface->get_player_list_cmd(rawAddress, start, end);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending getPlayerListNative command, status: %d", status);
}
env->ReleaseByteArrayElements(address, addr, 0);
}
static void changeFolderPathNative(JNIEnv* env, jobject object,
jbyteArray address, jbyte direction,
jlong uid) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return;
}
// jbyte* uid = env->GetByteArrayElements(uidarr, NULL);
// if (!uid) {
// jniThrowIOException(env, EINVAL);
// return;
//}
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
bt_status_t status = sBluetoothAvrcpInterface->change_folder_path_cmd(
rawAddress, (uint8_t)direction, (uint8_t*)&uid);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending changeFolderPathNative command, status: %d", status);
}
// env->ReleaseByteArrayElements(address, addr, 0);
}
static void setBrowsedPlayerNative(JNIEnv* env, jobject object,
jbyteArray address, jint id) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return;
}
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
bt_status_t status = sBluetoothAvrcpInterface->set_browsed_player_cmd(
rawAddress, (uint16_t)id);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending setBrowsedPlayerNative command, status: %d", status);
}
env->ReleaseByteArrayElements(address, addr, 0);
}
static void setAddressedPlayerNative(JNIEnv* env, jobject object,
jbyteArray address, jint id) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return;
}
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
bt_status_t status = sBluetoothAvrcpInterface->set_addressed_player_cmd(
rawAddress, (uint16_t)id);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending setAddressedPlayerNative command, status: %d",
status);
}
env->ReleaseByteArrayElements(address, addr, 0);
}
static void playItemNative(JNIEnv* env, jobject object, jbyteArray address,
jbyte scope, jlong uid, jint uidCounter) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return;
}
// jbyte* uid = env->GetByteArrayElements(uidArr, NULL);
// if (!uid) {
// jniThrowIOException(env, EINVAL);
// return;
// }
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
bt_status_t status = sBluetoothAvrcpInterface->play_item_cmd(
rawAddress, (uint8_t)scope, (uint8_t*)&uid, (uint16_t)uidCounter);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending playItemNative command, status: %d", status);
}
env->ReleaseByteArrayElements(address, addr, 0);
}
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void*)classInitNative},
{"initNative", "()V", (void*)initNative},
{"cleanupNative", "()V", (void*)cleanupNative},
{"sendPassThroughCommandNative", "([BII)Z",
(void*)sendPassThroughCommandNative},
{"sendGroupNavigationCommandNative", "([BII)Z",
(void*)sendGroupNavigationCommandNative},
{"setPlayerApplicationSettingValuesNative", "([BB[B[B)V",
(void*)setPlayerApplicationSettingValuesNative},
{"sendAbsVolRspNative", "([BII)V", (void*)sendAbsVolRspNative},
{"sendRegisterAbsVolRspNative", "([BBII)V",
(void*)sendRegisterAbsVolRspNative},
{"getPlaybackStateNative", "([B)V", (void*)getPlaybackStateNative},
{"getNowPlayingListNative", "([BII)V", (void*)getNowPlayingListNative},
{"getFolderListNative", "([BII)V", (void*)getFolderListNative},
{"getPlayerListNative", "([BII)V", (void*)getPlayerListNative},
{"changeFolderPathNative", "([BBJ)V", (void*)changeFolderPathNative},
{"playItemNative", "([BBJI)V", (void*)playItemNative},
{"setBrowsedPlayerNative", "([BI)V", (void*)setBrowsedPlayerNative},
{"setAddressedPlayerNative", "([BI)V", (void*)setAddressedPlayerNative},
};
int register_com_android_bluetooth_avrcp_controller(JNIEnv* env) {
return jniRegisterNativeMethods(
env, "com/android/bluetooth/avrcpcontroller/AvrcpControllerService",
sMethods, NELEM(sMethods));
}
}