| /* |
| * 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 "BluetoothA2dpServiceJni" |
| |
| #define LOG_NDEBUG 0 |
| |
| #include "com_android_bluetooth.h" |
| #include "hardware/bt_av.h" |
| #include "utils/Log.h" |
| #include "android_runtime/AndroidRuntime.h" |
| |
| #include <string.h> |
| |
| namespace android { |
| static jmethodID method_onConnectionStateChanged; |
| static jmethodID method_onAudioStateChanged; |
| |
| static const btav_interface_t *sBluetoothA2dpInterface = 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 |
| //if (sCallbackEnv == NULL) { |
| sCallbackEnv = getCallbackEnv(); |
| //} |
| |
| JNIEnv* env = AndroidRuntime::getJNIEnv(); |
| if (sCallbackEnv != env || sCallbackEnv == NULL) return false; |
| return true; |
| } |
| |
| static void bta2dp_connection_state_callback(btav_connection_state_t state, bt_bdaddr_t* bd_addr) { |
| jbyteArray addr; |
| |
| ALOGI("%s", __FUNCTION__); |
| |
| 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_onConnectionStateChanged, (jint) state, |
| addr); |
| checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); |
| sCallbackEnv->DeleteLocalRef(addr); |
| } |
| |
| static void bta2dp_audio_state_callback(btav_audio_state_t state, bt_bdaddr_t* bd_addr) { |
| jbyteArray addr; |
| |
| ALOGI("%s", __FUNCTION__); |
| |
| 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_onAudioStateChanged, (jint) state, |
| addr); |
| checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); |
| sCallbackEnv->DeleteLocalRef(addr); |
| } |
| |
| static btav_callbacks_t sBluetoothA2dpCallbacks = { |
| sizeof(sBluetoothA2dpCallbacks), |
| bta2dp_connection_state_callback, |
| bta2dp_audio_state_callback |
| }; |
| |
| static void classInitNative(JNIEnv* env, jclass clazz) { |
| int err; |
| const bt_interface_t* btInf; |
| bt_status_t status; |
| |
| method_onConnectionStateChanged = |
| env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); |
| |
| method_onAudioStateChanged = |
| env->GetMethodID(clazz, "onAudioStateChanged", "(I[B)V"); |
| /* |
| if ( (btInf = getBluetoothInterface()) == NULL) { |
| ALOGE("Bluetooth module is not loaded"); |
| return; |
| } |
| |
| if ( (sBluetoothA2dpInterface = (btav_interface_t *) |
| btInf->get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID)) == NULL) { |
| ALOGE("Failed to get Bluetooth A2DP Interface"); |
| return; |
| } |
| */ |
| |
| // TODO(BT) do this only once or |
| // Do we need to do this every time the BT reenables? |
| /* |
| if ( (status = sBluetoothA2dpInterface->init(&sBluetoothA2dpCallbacks)) != BT_STATUS_SUCCESS) { |
| ALOGE("Failed to initialize Bluetooth A2DP, status: %d", status); |
| sBluetoothA2dpInterface = NULL; |
| return; |
| }*/ |
| |
| 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 (sBluetoothA2dpInterface !=NULL) { |
| ALOGW("Cleaning up A2DP Interface before initializing..."); |
| sBluetoothA2dpInterface->cleanup(); |
| sBluetoothA2dpInterface = NULL; |
| } |
| |
| if (mCallbacksObj != NULL) { |
| ALOGW("Cleaning up A2DP callback object"); |
| env->DeleteGlobalRef(mCallbacksObj); |
| mCallbacksObj = NULL; |
| } |
| |
| if ( (sBluetoothA2dpInterface = (btav_interface_t *) |
| btInf->get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID)) == NULL) { |
| ALOGE("Failed to get Bluetooth A2DP Interface"); |
| return; |
| } |
| |
| if ( (status = sBluetoothA2dpInterface->init(&sBluetoothA2dpCallbacks)) != BT_STATUS_SUCCESS) { |
| ALOGE("Failed to initialize Bluetooth A2DP, status: %d", status); |
| sBluetoothA2dpInterface = NULL; |
| return; |
| } |
| |
| mCallbacksObj = env->NewGlobalRef(object); |
| } |
| |
| static void cleanupNative(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 (sBluetoothA2dpInterface !=NULL) { |
| sBluetoothA2dpInterface->cleanup(); |
| sBluetoothA2dpInterface = NULL; |
| } |
| |
| if (mCallbacksObj != NULL) { |
| env->DeleteGlobalRef(mCallbacksObj); |
| mCallbacksObj = NULL; |
| } |
| } |
| |
| static jboolean connectA2dpNative(JNIEnv *env, jobject object, jbyteArray address) { |
| jbyte *addr; |
| bt_bdaddr_t * btAddr; |
| bt_status_t status; |
| |
| ALOGI("%s: sBluetoothA2dpInterface: %p", __FUNCTION__, sBluetoothA2dpInterface); |
| if (!sBluetoothA2dpInterface) return JNI_FALSE; |
| |
| addr = env->GetByteArrayElements(address, NULL); |
| btAddr = (bt_bdaddr_t *) addr; |
| if (!addr) { |
| jniThrowIOException(env, EINVAL); |
| return JNI_FALSE; |
| } |
| |
| if ((status = sBluetoothA2dpInterface->connect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) { |
| ALOGE("Failed HF connection, status: %d", status); |
| } |
| env->ReleaseByteArrayElements(address, addr, 0); |
| return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; |
| } |
| |
| static jboolean disconnectA2dpNative(JNIEnv *env, jobject object, jbyteArray address) { |
| jbyte *addr; |
| bt_status_t status; |
| |
| if (!sBluetoothA2dpInterface) return JNI_FALSE; |
| |
| addr = env->GetByteArrayElements(address, NULL); |
| if (!addr) { |
| jniThrowIOException(env, EINVAL); |
| return JNI_FALSE; |
| } |
| |
| if ( (status = sBluetoothA2dpInterface->disconnect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) { |
| ALOGE("Failed HF disconnection, status: %d", status); |
| } |
| env->ReleaseByteArrayElements(address, addr, 0); |
| return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; |
| } |
| |
| static JNINativeMethod sMethods[] = { |
| {"classInitNative", "()V", (void *) classInitNative}, |
| {"initNative", "()V", (void *) initNative}, |
| {"cleanupNative", "()V", (void *) cleanupNative}, |
| {"connectA2dpNative", "([B)Z", (void *) connectA2dpNative}, |
| {"disconnectA2dpNative", "([B)Z", (void *) disconnectA2dpNative}, |
| }; |
| |
| int register_com_android_bluetooth_a2dp(JNIEnv* env) |
| { |
| return jniRegisterNativeMethods(env, "com/android/bluetooth/a2dp/A2dpStateMachine", |
| sMethods, NELEM(sMethods)); |
| } |
| |
| } |