/* ** Copyright 2008, 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 "BluetoothA2dpService.cpp" #include "android_bluetooth_common.h" #include "android_runtime/AndroidRuntime.h" #include "JNIHelp.h" #include "jni.h" #include "utils/Log.h" #include "utils/misc.h" #include #include #include #include #include #include #ifdef HAVE_BLUETOOTH #include #endif namespace android { #ifdef HAVE_BLUETOOTH static jmethodID method_onHeadsetCreated; static jmethodID method_onHeadsetRemoved; static jmethodID method_onSinkConnected; static jmethodID method_onSinkDisconnected; static jmethodID method_onSinkPlaying; static jmethodID method_onSinkStopped; typedef struct { JNIEnv *env; DBusConnection *conn; jobject me; // for callbacks to java } native_data_t; static native_data_t *nat = NULL; // global native data #endif #ifdef HAVE_BLUETOOTH static void onConnectSinkResult(DBusMessage *msg, void *user, void *nat); static void onDisconnectSinkResult(DBusMessage *msg, void *user, void *nat); #endif /* Returns true on success (even if adapter is present but disabled). * Return false if dbus is down, or another serious error (out of memory) */ static bool initNative(JNIEnv* env, jobject object) { LOGV(__FUNCTION__); #ifdef HAVE_BLUETOOTH nat = (native_data_t *)calloc(1, sizeof(native_data_t)); if (NULL == nat) { LOGE("%s: out of memory!", __FUNCTION__); return false; } nat->env = env; nat->me = env->NewGlobalRef(object); DBusError err; dbus_error_init(&err); dbus_threads_init_default(); nat->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); if (dbus_error_is_set(&err)) { LOGE("Could not get onto the system bus: %s", err.message); dbus_error_free(&err); return false; } #endif /*HAVE_BLUETOOTH*/ return true; } static void cleanupNative(JNIEnv* env, jobject object) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { dbus_connection_close(nat->conn); env->DeleteGlobalRef(nat->me); free(nat); nat = NULL; } #endif } static jobjectArray listHeadsetsNative(JNIEnv *env, jobject object) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { DBusMessage *reply = dbus_func_args(env, nat->conn, "/org/bluez/audio", "org.bluez.audio.Manager", "ListHeadsets", DBUS_TYPE_INVALID); return reply ? dbus_returns_array_of_strings(env, reply) : NULL; } #endif return NULL; } static jstring createHeadsetNative(JNIEnv *env, jobject object, jstring address) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { const char *c_address = env->GetStringUTFChars(address, NULL); LOGV("... address = %s\n", c_address); DBusMessage *reply = dbus_func_args(env, nat->conn, "/org/bluez/audio", "org.bluez.audio.Manager", "CreateHeadset", DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(address, c_address); return reply ? dbus_returns_string(env, reply) : NULL; } #endif return NULL; } static jstring removeHeadsetNative(JNIEnv *env, jobject object, jstring path) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); DBusMessage *reply = dbus_func_args(env, nat->conn, "/org/bluez/audio", "org.bluez.audio.Manager", "RemoveHeadset", DBUS_TYPE_STRING, &c_path, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); return reply ? dbus_returns_string(env, reply) : NULL; } #endif return NULL; } static jstring getAddressNative(JNIEnv *env, jobject object, jstring path) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); DBusMessage *reply = dbus_func_args(env, nat->conn, c_path, "org.bluez.audio.Device", "GetAddress", DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); return reply ? dbus_returns_string(env, reply) : NULL; } #endif return NULL; } static jboolean connectSinkNative(JNIEnv *env, jobject object, jstring path) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); size_t path_sz = env->GetStringUTFLength(path) + 1; char *c_path_copy = (char *)malloc(path_sz); // callback data strncpy(c_path_copy, c_path, path_sz); bool ret = dbus_func_args_async(env, nat->conn, -1, onConnectSinkResult, (void *)c_path_copy, nat, c_path, "org.bluez.audio.Sink", "Connect", DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); if (!ret) { free(c_path_copy); return JNI_FALSE; } return JNI_TRUE; } #endif return JNI_FALSE; } static jboolean disconnectSinkNative(JNIEnv *env, jobject object, jstring path) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); size_t path_sz = env->GetStringUTFLength(path) + 1; char *c_path_copy = (char *)malloc(path_sz); // callback data strncpy(c_path_copy, c_path, path_sz); bool ret = dbus_func_args_async(env, nat->conn, -1, onDisconnectSinkResult, (void *)c_path_copy, nat, c_path, "org.bluez.audio.Sink", "Disconnect", DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); if (!ret) { free(c_path_copy); return JNI_FALSE; } return JNI_TRUE; } #endif return JNI_FALSE; } static jboolean isSinkConnectedNative(JNIEnv *env, jobject object, jstring path) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); DBusMessage *reply = dbus_func_args(env, nat->conn, c_path, "org.bluez.audio.Sink", "IsConnected", DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); return reply ? dbus_returns_boolean(env, reply) : JNI_FALSE; } #endif return JNI_FALSE; } #ifdef HAVE_BLUETOOTH static void onConnectSinkResult(DBusMessage *msg, void *user, void *natData) { LOGV(__FUNCTION__); char *c_path = (char *)user; DBusError err; dbus_error_init(&err); JNIEnv *env = nat->env; LOGV("... path = %s", c_path); if (dbus_set_error_from_message(&err, msg)) { /* if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationFailed")) */ LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); dbus_error_free(&err); env->CallVoidMethod(nat->me, method_onSinkDisconnected, env->NewStringUTF(c_path)); if (env->ExceptionCheck()) { LOGE("VM Exception occurred in native function %s (%s:%d)", __FUNCTION__, __FILE__, __LINE__); } } // else Java callback is triggered by signal in a2dp_event_filter free(c_path); } static void onDisconnectSinkResult(DBusMessage *msg, void *user, void *natData) { LOGV(__FUNCTION__); char *c_path = (char *)user; DBusError err; dbus_error_init(&err); JNIEnv *env = nat->env; LOGV("... path = %s", c_path); if (dbus_set_error_from_message(&err, msg)) { /* if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationFailed")) */ LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); if (strcmp(err.name, "org.bluez.Error.NotConnected") == 0) { // we were already disconnected, so report disconnect env->CallVoidMethod(nat->me, method_onSinkDisconnected, env->NewStringUTF(c_path)); } else { // Assume it is still connected env->CallVoidMethod(nat->me, method_onSinkConnected, env->NewStringUTF(c_path)); } dbus_error_free(&err); if (env->ExceptionCheck()) { LOGE("VM Exception occurred in native function %s (%s:%d)", __FUNCTION__, __FILE__, __LINE__); } } // else Java callback is triggered by signal in a2dp_event_filter free(c_path); } DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) { DBusError err; if (!nat) { LOGV("... skipping %s\n", __FUNCTION__); LOGV("... ignored\n"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_error_init(&err); if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } DBusHandlerResult result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (dbus_message_is_signal(msg, "org.bluez.audio.Manager", "HeadsetCreated")) { char *c_path; if (dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &c_path, DBUS_TYPE_INVALID)) { LOGV("... path = %s", c_path); env->CallVoidMethod(nat->me, method_onHeadsetCreated, env->NewStringUTF(c_path)); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); result = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, "org.bluez.audio.Manager", "HeadsetRemoved")) { char *c_path; if (dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &c_path, DBUS_TYPE_INVALID)) { LOGV("... path = %s", c_path); env->CallVoidMethod(nat->me, method_onHeadsetRemoved, env->NewStringUTF(c_path)); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); result = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, "org.bluez.audio.Sink", "Connected")) { const char *c_path = dbus_message_get_path(msg); LOGV("... path = %s", c_path); env->CallVoidMethod(nat->me, method_onSinkConnected, env->NewStringUTF(c_path)); result = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, "org.bluez.audio.Sink", "Disconnected")) { const char *c_path = dbus_message_get_path(msg); LOGV("... path = %s", c_path); env->CallVoidMethod(nat->me, method_onSinkDisconnected, env->NewStringUTF(c_path)); result = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, "org.bluez.audio.Sink", "Playing")) { const char *c_path = dbus_message_get_path(msg); LOGV("... path = %s", c_path); env->CallVoidMethod(nat->me, method_onSinkPlaying, env->NewStringUTF(c_path)); result = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, "org.bluez.audio.Sink", "Stopped")) { const char *c_path = dbus_message_get_path(msg); LOGV("... path = %s", c_path); env->CallVoidMethod(nat->me, method_onSinkStopped, env->NewStringUTF(c_path)); result = DBUS_HANDLER_RESULT_HANDLED; } if (result == DBUS_HANDLER_RESULT_NOT_YET_HANDLED) { LOGV("... ignored"); } if (env->ExceptionCheck()) { LOGE("VM Exception occurred while handling %s.%s (%s) in %s," " leaving for VM", dbus_message_get_interface(msg), dbus_message_get_member(msg), dbus_message_get_path(msg), __FUNCTION__); } return result; } #endif static JNINativeMethod sMethods[] = { {"initNative", "()Z", (void *)initNative}, {"cleanupNative", "()V", (void *)cleanupNative}, /* Bluez audio 3.36 API */ {"listHeadsetsNative", "()[Ljava/lang/String;", (void*)listHeadsetsNative}, {"createHeadsetNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*)createHeadsetNative}, {"removeHeadsetNative", "(Ljava/lang/String;)Z", (void*)removeHeadsetNative}, {"getAddressNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getAddressNative}, {"connectSinkNative", "(Ljava/lang/String;)Z", (void*)connectSinkNative}, {"disconnectSinkNative", "(Ljava/lang/String;)Z", (void*)disconnectSinkNative}, {"isSinkConnectedNative", "(Ljava/lang/String;)Z", (void*)isSinkConnectedNative}, }; int register_android_server_BluetoothA2dpService(JNIEnv *env) { jclass clazz = env->FindClass("android/server/BluetoothA2dpService"); if (clazz == NULL) { LOGE("Can't find android/server/BluetoothA2dpService"); return -1; } #ifdef HAVE_BLUETOOTH method_onHeadsetCreated = env->GetMethodID(clazz, "onHeadsetCreated", "(Ljava/lang/String;)V"); method_onHeadsetRemoved = env->GetMethodID(clazz, "onHeadsetRemoved", "(Ljava/lang/String;)V"); method_onSinkConnected = env->GetMethodID(clazz, "onSinkConnected", "(Ljava/lang/String;)V"); method_onSinkDisconnected = env->GetMethodID(clazz, "onSinkDisconnected", "(Ljava/lang/String;)V"); method_onSinkPlaying = env->GetMethodID(clazz, "onSinkPlaying", "(Ljava/lang/String;)V"); method_onSinkStopped = env->GetMethodID(clazz, "onSinkStopped", "(Ljava/lang/String;)V"); #endif return AndroidRuntime::registerNativeMethods(env, "android/server/BluetoothA2dpService", sMethods, NELEM(sMethods)); } } /* namespace android */