blob: 062f8933b23acf4da0ba6c0c97842b3a01822b87 [file] [log] [blame]
/*
** 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 <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#ifdef HAVE_BLUETOOTH
#include <dbus/dbus.h>
#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
extern event_loop_native_data_t *event_loop_nat; // for the event loop JNIEnv
#endif
#ifdef HAVE_BLUETOOTH
static void onConnectSinkResult(DBusMessage *msg, void *user);
static void onDisconnectSinkResult(DBusMessage *msg, void *user);
#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, 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, 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) {
LOGV(__FUNCTION__);
char *c_path = (char *)user;
DBusError err;
dbus_error_init(&err);
JNIEnv *env = event_loop_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) {
LOGV(__FUNCTION__);
char *c_path = (char *)user;
DBusError err;
dbus_error_init(&err);
JNIEnv *env = event_loop_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 */