blob: 8c795afe3ee4c2f9c8c7063586bb6cfa25930cd4 [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_onSinkPropertyChanged;
static jmethodID method_onConnectSinkResult;
typedef struct {
JavaVM *vm;
int envVer;
DBusConnection *conn;
jobject me; // for callbacks to java
} native_data_t;
static native_data_t *nat = NULL; // global native data
static void onConnectSinkResult(DBusMessage *msg, void *user, void *n);
static Properties sink_properties[] = {
{"State", DBUS_TYPE_STRING},
{"Connected", DBUS_TYPE_BOOLEAN},
{"Playing", DBUS_TYPE_BOOLEAN},
};
#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;
}
env->GetJavaVM( &(nat->vm) );
nat->envVer = env->GetVersion();
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;
}
dbus_connection_set_exit_on_disconnect(nat->conn, 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 getSinkPropertiesNative(JNIEnv *env, jobject object,
jstring path) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
if (nat) {
DBusMessage *msg, *reply;
DBusError err;
dbus_error_init(&err);
const char *c_path = env->GetStringUTFChars(path, NULL);
reply = dbus_func_args_timeout(env,
nat->conn, -1, c_path,
"org.bluez.AudioSink", "GetProperties",
DBUS_TYPE_INVALID);
env->ReleaseStringUTFChars(path, c_path);
if (!reply && dbus_error_is_set(&err)) {
LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
return NULL;
} else if (!reply) {
LOGE("DBus reply is NULL in function %s", __FUNCTION__);
return NULL;
}
DBusMessageIter iter;
if (dbus_message_iter_init(reply, &iter))
return parse_properties(env, &iter, (Properties *)&sink_properties,
sizeof(sink_properties) / sizeof(Properties));
}
#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);
int len = env->GetStringLength(path) + 1;
char *context_path = (char *)calloc(len, sizeof(char));
strlcpy(context_path, c_path, len); // for callback
bool ret = dbus_func_args_async(env, nat->conn, -1, onConnectSinkResult, context_path,
nat, c_path, "org.bluez.AudioSink", "Connect",
DBUS_TYPE_INVALID);
env->ReleaseStringUTFChars(path, c_path);
return ret ? JNI_TRUE : JNI_FALSE;
}
#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);
bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
c_path, "org.bluez.AudioSink", "Disconnect",
DBUS_TYPE_INVALID);
env->ReleaseStringUTFChars(path, c_path);
return ret ? JNI_TRUE : JNI_FALSE;
}
#endif
return JNI_FALSE;
}
static jboolean suspendSinkNative(JNIEnv *env, jobject object,
jstring path) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
if (nat) {
const char *c_path = env->GetStringUTFChars(path, NULL);
bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
c_path, "org.bluez.audio.Sink", "Suspend",
DBUS_TYPE_INVALID);
env->ReleaseStringUTFChars(path, c_path);
return ret ? JNI_TRUE : JNI_FALSE;
}
#endif
return JNI_FALSE;
}
static jboolean resumeSinkNative(JNIEnv *env, jobject object,
jstring path) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
if (nat) {
const char *c_path = env->GetStringUTFChars(path, NULL);
bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
c_path, "org.bluez.audio.Sink", "Resume",
DBUS_TYPE_INVALID);
env->ReleaseStringUTFChars(path, c_path);
return ret ? JNI_TRUE : JNI_FALSE;
}
#endif
return JNI_FALSE;
}
static jboolean avrcpVolumeUpNative(JNIEnv *env, jobject object,
jstring path) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
if (nat) {
const char *c_path = env->GetStringUTFChars(path, NULL);
bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
c_path, "org.bluez.Control", "VolumeUp",
DBUS_TYPE_INVALID);
env->ReleaseStringUTFChars(path, c_path);
return ret ? JNI_TRUE : JNI_FALSE;
}
#endif
return JNI_FALSE;
}
static jboolean avrcpVolumeDownNative(JNIEnv *env, jobject object,
jstring path) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
if (nat) {
const char *c_path = env->GetStringUTFChars(path, NULL);
bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
c_path, "org.bluez.Control", "VolumeDown",
DBUS_TYPE_INVALID);
env->ReleaseStringUTFChars(path, c_path);
return ret ? JNI_TRUE : JNI_FALSE;
}
#endif
return JNI_FALSE;
}
#ifdef HAVE_BLUETOOTH
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.AudioSink",
"PropertyChanged")) {
jobjectArray str_array =
parse_property_change(env, msg, (Properties *)&sink_properties,
sizeof(sink_properties) / sizeof(Properties));
const char *c_path = dbus_message_get_path(msg);
jstring path = env->NewStringUTF(c_path);
env->CallVoidMethod(nat->me,
method_onSinkPropertyChanged,
path,
str_array);
env->DeleteLocalRef(path);
result = DBUS_HANDLER_RESULT_HANDLED;
return result;
} else {
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;
}
void onConnectSinkResult(DBusMessage *msg, void *user, void *n) {
LOGV(__FUNCTION__);
native_data_t *nat = (native_data_t *)n;
const char *path = (const char *)user;
DBusError err;
dbus_error_init(&err);
JNIEnv *env;
nat->vm->GetEnv((void**)&env, nat->envVer);
bool result = JNI_TRUE;
if (dbus_set_error_from_message(&err, msg)) {
LOG_AND_FREE_DBUS_ERROR(&err);
result = JNI_FALSE;
}
LOGV("... Device Path = %s, result = %d", path, result);
jstring jPath = env->NewStringUTF(path);
env->CallVoidMethod(nat->me,
method_onConnectSinkResult,
jPath,
result);
env->DeleteLocalRef(jPath);
free(user);
}
#endif
static JNINativeMethod sMethods[] = {
{"initNative", "()Z", (void *)initNative},
{"cleanupNative", "()V", (void *)cleanupNative},
/* Bluez audio 4.47 API */
{"connectSinkNative", "(Ljava/lang/String;)Z", (void *)connectSinkNative},
{"disconnectSinkNative", "(Ljava/lang/String;)Z", (void *)disconnectSinkNative},
{"suspendSinkNative", "(Ljava/lang/String;)Z", (void*)suspendSinkNative},
{"resumeSinkNative", "(Ljava/lang/String;)Z", (void*)resumeSinkNative},
{"getSinkPropertiesNative", "(Ljava/lang/String;)[Ljava/lang/Object;",
(void *)getSinkPropertiesNative},
{"avrcpVolumeUpNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeUpNative},
{"avrcpVolumeDownNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeDownNative},
};
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_onSinkPropertyChanged = env->GetMethodID(clazz, "onSinkPropertyChanged",
"(Ljava/lang/String;[Ljava/lang/String;)V");
method_onConnectSinkResult = env->GetMethodID(clazz, "onConnectSinkResult",
"(Ljava/lang/String;Z)V");
#endif
return AndroidRuntime::registerNativeMethods(env,
"android/server/BluetoothA2dpService", sMethods, NELEM(sMethods));
}
} /* namespace android */