/* * Copyright (C) 2012 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 "BluetoothAvrcpServiceJni" #define LOG_NDEBUG 0 #include "com_android_bluetooth.h" #include "hardware/bt_rc.h" #include "utils/Log.h" #include "android_runtime/AndroidRuntime.h" #include <string.h> namespace android { static jmethodID method_getRcFeatures; static jmethodID method_getPlayStatus; static jmethodID method_getElementAttr; static jmethodID method_registerNotification; static jmethodID method_volumeChangeCallback; static jmethodID method_handlePassthroughCmd; static const btrc_interface_t *sBluetoothAvrcpInterface = NULL; static jobject mCallbacksObj = NULL; static JNIEnv *sCallbackEnv = NULL; static bool checkCallbackThread() { // Always fetch the latest callbackEnv from AdapterService. // Caching this could cause this sCallbackEnv to go out-of-sync // with the AdapterService's ENV if an ASSOCIATE/DISASSOCIATE event // is received sCallbackEnv = getCallbackEnv(); JNIEnv* env = AndroidRuntime::getJNIEnv(); if (sCallbackEnv != env || sCallbackEnv == NULL) return false; return true; } static void btavrcp_remote_features_callback(bt_bdaddr_t* bd_addr, btrc_remote_features_t features) { ALOGI("%s", __FUNCTION__); jbyteArray addr; if (!checkCallbackThread()) { ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); return; } addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t)); if (!addr) { ALOGE("Fail to new jbyteArray bd addr for connection state"); checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); return; } sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getRcFeatures, addr, (jint)features); checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); } static void btavrcp_get_play_status_callback() { ALOGI("%s", __FUNCTION__); if (!checkCallbackThread()) { ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); return; } sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getPlayStatus); checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); } static void btavrcp_get_element_attr_callback(uint8_t num_attr, btrc_media_attr_t *p_attrs) { jintArray attrs; ALOGI("%s", __FUNCTION__); if (!checkCallbackThread()) { ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); return; } attrs = (jintArray)sCallbackEnv->NewIntArray(num_attr); if (!attrs) { ALOGE("Fail to new jintArray for attrs"); checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); return; } sCallbackEnv->SetIntArrayRegion(attrs, 0, num_attr, (jint *)p_attrs); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getElementAttr, (jbyte)num_attr, attrs); checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); sCallbackEnv->DeleteLocalRef(attrs); } static void btavrcp_register_notification_callback(btrc_event_id_t event_id, uint32_t param) { ALOGI("%s", __FUNCTION__); if (!checkCallbackThread()) { ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); return; } sCallbackEnv->CallVoidMethod(mCallbacksObj, method_registerNotification, (jint)event_id, (jint)param); checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); } static void btavrcp_volume_change_callback(uint8_t volume, uint8_t ctype) { ALOGI("%s", __FUNCTION__); if (!checkCallbackThread()) { ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); return; } sCallbackEnv->CallVoidMethod(mCallbacksObj, method_volumeChangeCallback, (jint)volume, (jint)ctype); checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); } static void btavrcp_passthrough_command_callback(int id, int pressed) { ALOGI("%s", __FUNCTION__); if (!checkCallbackThread()) { ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); return; } sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handlePassthroughCmd, (jint)id, (jint)pressed); checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); } static btrc_callbacks_t sBluetoothAvrcpCallbacks = { sizeof(sBluetoothAvrcpCallbacks), btavrcp_remote_features_callback, btavrcp_get_play_status_callback, NULL, NULL, NULL, NULL, NULL, NULL, btavrcp_get_element_attr_callback, btavrcp_register_notification_callback, btavrcp_volume_change_callback, btavrcp_passthrough_command_callback }; static void classInitNative(JNIEnv* env, jclass clazz) { method_getRcFeatures = env->GetMethodID(clazz, "getRcFeatures", "([BI)V"); method_getPlayStatus = env->GetMethodID(clazz, "getPlayStatus", "()V"); method_getElementAttr = env->GetMethodID(clazz, "getElementAttr", "(B[I)V"); method_registerNotification = env->GetMethodID(clazz, "registerNotification", "(II)V"); method_volumeChangeCallback = env->GetMethodID(clazz, "volumeChangeCallback", "(II)V"); method_handlePassthroughCmd = env->GetMethodID(clazz, "handlePassthroughCmd", "(II)V"); ALOGI("%s: succeeds", __FUNCTION__); } static void initNative(JNIEnv *env, jobject object) { const bt_interface_t* btInf; bt_status_t status; if ( (btInf = getBluetoothInterface()) == NULL) { ALOGE("Bluetooth module is not loaded"); return; } if (sBluetoothAvrcpInterface !=NULL) { ALOGW("Cleaning up Avrcp Interface before initializing..."); sBluetoothAvrcpInterface->cleanup(); sBluetoothAvrcpInterface = NULL; } if (mCallbacksObj != NULL) { ALOGW("Cleaning up Avrcp callback object"); env->DeleteGlobalRef(mCallbacksObj); mCallbacksObj = NULL; } if ( (sBluetoothAvrcpInterface = (btrc_interface_t *) btInf->get_profile_interface(BT_PROFILE_AV_RC_ID)) == NULL) { ALOGE("Failed to get Bluetooth Avrcp Interface"); return; } if ( (status = sBluetoothAvrcpInterface->init(&sBluetoothAvrcpCallbacks)) != BT_STATUS_SUCCESS) { ALOGE("Failed to initialize Bluetooth Avrcp, status: %d", status); sBluetoothAvrcpInterface = NULL; return; } mCallbacksObj = env->NewGlobalRef(object); } static void cleanupNative(JNIEnv *env, jobject object) { const bt_interface_t* btInf; if ( (btInf = getBluetoothInterface()) == NULL) { ALOGE("Bluetooth module is not loaded"); return; } if (sBluetoothAvrcpInterface !=NULL) { sBluetoothAvrcpInterface->cleanup(); sBluetoothAvrcpInterface = NULL; } if (mCallbacksObj != NULL) { env->DeleteGlobalRef(mCallbacksObj); mCallbacksObj = NULL; } } static jboolean getPlayStatusRspNative(JNIEnv *env, jobject object, jint playStatus, jint songLen, jint songPos) { bt_status_t status; ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface); if (!sBluetoothAvrcpInterface) return JNI_FALSE; if ((status = sBluetoothAvrcpInterface->get_play_status_rsp((btrc_play_status_t)playStatus, songLen, songPos)) != BT_STATUS_SUCCESS) { ALOGE("Failed get_play_status_rsp, status: %d", status); } return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean getElementAttrRspNative(JNIEnv *env, jobject object, jbyte numAttr, jintArray attrIds, jobjectArray textArray) { jint *attr; bt_status_t status; jstring text; int i; btrc_element_attr_val_t *pAttrs = NULL; const char* textStr; if (!sBluetoothAvrcpInterface) return JNI_FALSE; if (numAttr > BTRC_MAX_ELEM_ATTR_SIZE) { ALOGE("get_element_attr_rsp: number of attributes exceed maximum"); return JNI_FALSE; } pAttrs = new btrc_element_attr_val_t[numAttr]; if (!pAttrs) { ALOGE("get_element_attr_rsp: not have enough memeory"); return JNI_FALSE; } attr = env->GetIntArrayElements(attrIds, NULL); if (!attr) { delete[] pAttrs; jniThrowIOException(env, EINVAL); return JNI_FALSE; } for (i = 0; i < numAttr; ++i) { text = (jstring) env->GetObjectArrayElement(textArray, i); textStr = env->GetStringUTFChars(text, NULL); if (!textStr) { ALOGE("get_element_attr_rsp: GetStringUTFChars return NULL"); env->DeleteLocalRef(text); break; } pAttrs[i].attr_id = attr[i]; if (strlen(textStr) >= BTRC_MAX_ATTR_STR_LEN) { ALOGE("get_element_attr_rsp: string length exceed maximum"); strncpy((char *)pAttrs[i].text, textStr, BTRC_MAX_ATTR_STR_LEN-1); pAttrs[i].text[BTRC_MAX_ATTR_STR_LEN-1] = 0; } else { strcpy((char *)pAttrs[i].text, textStr); } env->ReleaseStringUTFChars(text, textStr); env->DeleteLocalRef(text); } if (i < numAttr) { delete[] pAttrs; env->ReleaseIntArrayElements(attrIds, attr, 0); return JNI_FALSE; } if ((status = sBluetoothAvrcpInterface->get_element_attr_rsp(numAttr, pAttrs)) != BT_STATUS_SUCCESS) { ALOGE("Failed get_element_attr_rsp, status: %d", status); } delete[] pAttrs; env->ReleaseIntArrayElements(attrIds, attr, 0); return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean registerNotificationRspPlayStatusNative(JNIEnv *env, jobject object, jint type, jint playStatus) { bt_status_t status; btrc_register_notification_t param; ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface); if (!sBluetoothAvrcpInterface) return JNI_FALSE; param.play_status = (btrc_play_status_t)playStatus; if ((status = sBluetoothAvrcpInterface->register_notification_rsp(BTRC_EVT_PLAY_STATUS_CHANGED, (btrc_notification_type_t)type, ¶m)) != BT_STATUS_SUCCESS) { ALOGE("Failed register_notification_rsp play status, status: %d", status); } return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean registerNotificationRspTrackChangeNative(JNIEnv *env, jobject object, jint type, jbyteArray track) { bt_status_t status; btrc_register_notification_t param; jbyte *trk; int i; ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface); if (!sBluetoothAvrcpInterface) return JNI_FALSE; trk = env->GetByteArrayElements(track, NULL); if (!trk) { jniThrowIOException(env, EINVAL); return JNI_FALSE; } for (i = 0; i < BTRC_UID_SIZE; ++i) { param.track[i] = trk[i]; } if ((status = sBluetoothAvrcpInterface->register_notification_rsp(BTRC_EVT_TRACK_CHANGE, (btrc_notification_type_t)type, ¶m)) != BT_STATUS_SUCCESS) { ALOGE("Failed register_notification_rsp track change, status: %d", status); } env->ReleaseByteArrayElements(track, trk, 0); return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean registerNotificationRspPlayPosNative(JNIEnv *env, jobject object, jint type, jint playPos) { bt_status_t status; btrc_register_notification_t param; ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface); if (!sBluetoothAvrcpInterface) return JNI_FALSE; param.song_pos = (uint32_t)playPos; if ((status = sBluetoothAvrcpInterface->register_notification_rsp(BTRC_EVT_PLAY_POS_CHANGED, (btrc_notification_type_t)type, ¶m)) != BT_STATUS_SUCCESS) { ALOGE("Failed register_notification_rsp play position, status: %d", status); } return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean setVolumeNative(JNIEnv *env, jobject object, jint volume) { bt_status_t status; //TODO: delete test code ALOGI("%s: jint: %d, uint8_t: %u", __FUNCTION__, volume, (uint8_t) volume); ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface); if (!sBluetoothAvrcpInterface) return JNI_FALSE; if ((status = sBluetoothAvrcpInterface->set_volume((uint8_t)volume)) != BT_STATUS_SUCCESS) { ALOGE("Failed set_volume, status: %d", status); } return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void *) classInitNative}, {"initNative", "()V", (void *) initNative}, {"cleanupNative", "()V", (void *) cleanupNative}, {"getPlayStatusRspNative", "(III)Z", (void *) getPlayStatusRspNative}, {"getElementAttrRspNative", "(B[I[Ljava/lang/String;)Z", (void *) getElementAttrRspNative}, {"registerNotificationRspPlayStatusNative", "(II)Z", (void *) registerNotificationRspPlayStatusNative}, {"registerNotificationRspTrackChangeNative", "(I[B)Z", (void *) registerNotificationRspTrackChangeNative}, {"registerNotificationRspPlayPosNative", "(II)Z", (void *) registerNotificationRspPlayPosNative}, {"setVolumeNative", "(I)Z", (void *) setVolumeNative} }; int register_com_android_bluetooth_avrcp(JNIEnv* env) { return jniRegisterNativeMethods(env, "com/android/bluetooth/a2dp/Avrcp", sMethods, NELEM(sMethods)); } }