1/* 2** Copyright 2008, The Android Open Source Project 3** 4** Licensed under the Apache License, Version 2.0 (the "License"); 5** you may not use this file except in compliance with the License. 6** You may obtain a copy of the License at 7** 8** http://www.apache.org/licenses/LICENSE-2.0 9** 10** Unless required by applicable law or agreed to in writing, software 11** distributed under the License is distributed on an "AS IS" BASIS, 12** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13** See the License for the specific language governing permissions and 14** limitations under the License. 15*/ 16 17#define LOG_TAG "BluetoothA2dpService.cpp" 18 19#include "android_bluetooth_common.h" 20#include "android_runtime/AndroidRuntime.h" 21#include "JNIHelp.h" 22#include "jni.h" 23#include "utils/Log.h" 24#include "utils/misc.h" 25 26#include <ctype.h> 27#include <errno.h> 28#include <stdio.h> 29#include <string.h> 30#include <stdlib.h> 31#include <unistd.h> 32 33#ifdef HAVE_BLUETOOTH 34#include <dbus/dbus.h> 35#endif 36 37namespace android { 38 39#ifdef HAVE_BLUETOOTH 40static jmethodID method_onHeadsetCreated; 41static jmethodID method_onHeadsetRemoved; 42static jmethodID method_onSinkConnected; 43static jmethodID method_onSinkDisconnected; 44static jmethodID method_onSinkPlaying; 45static jmethodID method_onSinkStopped; 46 47typedef struct { 48 JavaVM *vm; 49 int envVer; 50 DBusConnection *conn; 51 jobject me; // for callbacks to java 52} native_data_t; 53 54static native_data_t *nat = NULL; // global native data 55 56#endif 57 58#ifdef HAVE_BLUETOOTH 59static void onConnectSinkResult(DBusMessage *msg, void *user, void *nat); 60static void onDisconnectSinkResult(DBusMessage *msg, void *user, void *nat); 61#endif 62 63/* Returns true on success (even if adapter is present but disabled). 64 * Return false if dbus is down, or another serious error (out of memory) 65*/ 66static bool initNative(JNIEnv* env, jobject object) { 67 LOGV(__FUNCTION__); 68#ifdef HAVE_BLUETOOTH 69 nat = (native_data_t *)calloc(1, sizeof(native_data_t)); 70 if (NULL == nat) { 71 LOGE("%s: out of memory!", __FUNCTION__); 72 return false; 73 } 74 env->GetJavaVM( &(nat->vm) ); 75 nat->envVer = env->GetVersion(); 76 nat->me = env->NewGlobalRef(object); 77 78 DBusError err; 79 dbus_error_init(&err); 80 dbus_threads_init_default(); 81 nat->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); 82 if (dbus_error_is_set(&err)) { 83 LOGE("Could not get onto the system bus: %s", err.message); 84 dbus_error_free(&err); 85 return false; 86 } 87 dbus_connection_set_exit_on_disconnect(nat->conn, FALSE); 88#endif /*HAVE_BLUETOOTH*/ 89 return true; 90} 91 92static void cleanupNative(JNIEnv* env, jobject object) { 93#ifdef HAVE_BLUETOOTH 94 LOGV(__FUNCTION__); 95 if (nat) { 96 dbus_connection_close(nat->conn); 97 env->DeleteGlobalRef(nat->me); 98 free(nat); 99 nat = NULL; 100 } 101#endif 102} 103static jobjectArray listHeadsetsNative(JNIEnv *env, jobject object) { 104#ifdef HAVE_BLUETOOTH 105 LOGV(__FUNCTION__); 106 if (nat) { 107 DBusMessage *reply = 108 dbus_func_args(env, nat->conn, "/org/bluez/audio", 109 "org.bluez.audio.Manager", "ListHeadsets", 110 DBUS_TYPE_INVALID); 111 return reply ? dbus_returns_array_of_strings(env, reply) : NULL; 112 } 113#endif 114 return NULL; 115} 116 117static jstring createHeadsetNative(JNIEnv *env, jobject object, 118 jstring address) { 119#ifdef HAVE_BLUETOOTH 120 LOGV(__FUNCTION__); 121 if (nat) { 122 const char *c_address = env->GetStringUTFChars(address, NULL); 123 LOGV("... address = %s\n", c_address); 124 DBusMessage *reply = 125 dbus_func_args(env, nat->conn, "/org/bluez/audio", 126 "org.bluez.audio.Manager", "CreateHeadset", 127 DBUS_TYPE_STRING, &c_address, 128 DBUS_TYPE_INVALID); 129 env->ReleaseStringUTFChars(address, c_address); 130 return reply ? dbus_returns_string(env, reply) : NULL; 131 } 132#endif 133 return NULL; 134} 135 136static jstring removeHeadsetNative(JNIEnv *env, jobject object, jstring path) { 137#ifdef HAVE_BLUETOOTH 138 LOGV(__FUNCTION__); 139 if (nat) { 140 const char *c_path = env->GetStringUTFChars(path, NULL); 141 DBusMessage *reply = 142 dbus_func_args(env, nat->conn, "/org/bluez/audio", 143 "org.bluez.audio.Manager", "RemoveHeadset", 144 DBUS_TYPE_STRING, &c_path, 145 DBUS_TYPE_INVALID); 146 env->ReleaseStringUTFChars(path, c_path); 147 return reply ? dbus_returns_string(env, reply) : NULL; 148 } 149#endif 150 return NULL; 151} 152 153static jstring getAddressNative(JNIEnv *env, jobject object, jstring path) { 154#ifdef HAVE_BLUETOOTH 155 LOGV(__FUNCTION__); 156 if (nat) { 157 const char *c_path = env->GetStringUTFChars(path, NULL); 158 DBusMessage *reply = 159 dbus_func_args(env, nat->conn, c_path, 160 "org.bluez.audio.Device", "GetAddress", 161 DBUS_TYPE_INVALID); 162 env->ReleaseStringUTFChars(path, c_path); 163 return reply ? dbus_returns_string(env, reply) : NULL; 164 } 165#endif 166 return NULL; 167} 168 169static jboolean connectSinkNative(JNIEnv *env, jobject object, jstring path) { 170#ifdef HAVE_BLUETOOTH 171 LOGV(__FUNCTION__); 172 if (nat) { 173 const char *c_path = env->GetStringUTFChars(path, NULL); 174 size_t path_sz = env->GetStringUTFLength(path) + 1; 175 char *c_path_copy = (char *)malloc(path_sz); // callback data 176 strncpy(c_path_copy, c_path, path_sz); 177 178 bool ret = 179 dbus_func_args_async(env, nat->conn, -1, 180 onConnectSinkResult, (void *)c_path_copy, nat, 181 c_path, 182 "org.bluez.audio.Sink", "Connect", 183 DBUS_TYPE_INVALID); 184 185 env->ReleaseStringUTFChars(path, c_path); 186 if (!ret) { 187 free(c_path_copy); 188 return JNI_FALSE; 189 } 190 return JNI_TRUE; 191 } 192#endif 193 return JNI_FALSE; 194} 195 196static jboolean disconnectSinkNative(JNIEnv *env, jobject object, 197 jstring path) { 198#ifdef HAVE_BLUETOOTH 199 LOGV(__FUNCTION__); 200 if (nat) { 201 const char *c_path = env->GetStringUTFChars(path, NULL); 202 size_t path_sz = env->GetStringUTFLength(path) + 1; 203 char *c_path_copy = (char *)malloc(path_sz); // callback data 204 strncpy(c_path_copy, c_path, path_sz); 205 206 bool ret = 207 dbus_func_args_async(env, nat->conn, -1, 208 onDisconnectSinkResult, (void *)c_path_copy, nat, 209 c_path, 210 "org.bluez.audio.Sink", "Disconnect", 211 DBUS_TYPE_INVALID); 212 env->ReleaseStringUTFChars(path, c_path); 213 if (!ret) { 214 free(c_path_copy); 215 return JNI_FALSE; 216 } 217 return JNI_TRUE; 218 } 219#endif 220 return JNI_FALSE; 221} 222 223static jboolean isSinkConnectedNative(JNIEnv *env, jobject object, jstring path) { 224#ifdef HAVE_BLUETOOTH 225 LOGV(__FUNCTION__); 226 if (nat) { 227 const char *c_path = env->GetStringUTFChars(path, NULL); 228 DBusMessage *reply = 229 dbus_func_args(env, nat->conn, c_path, 230 "org.bluez.audio.Sink", "IsConnected", 231 DBUS_TYPE_INVALID); 232 env->ReleaseStringUTFChars(path, c_path); 233 return reply ? dbus_returns_boolean(env, reply) : JNI_FALSE; 234 } 235#endif 236 return JNI_FALSE; 237} 238 239#ifdef HAVE_BLUETOOTH 240static void onConnectSinkResult(DBusMessage *msg, void *user, void *natData) { 241 LOGV(__FUNCTION__); 242 243 char *c_path = (char *)user; 244 DBusError err; 245 JNIEnv *env; 246 247 if (nat->vm->GetEnv((void**)&env, nat->envVer) < 0) { 248 LOGE("%s: error finding Env for our VM\n", __FUNCTION__); 249 return; 250 } 251 252 dbus_error_init(&err); 253 254 LOGV("... path = %s", c_path); 255 if (dbus_set_error_from_message(&err, msg)) { 256 /* if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationFailed")) */ 257 LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); 258 dbus_error_free(&err); 259 env->CallVoidMethod(nat->me, 260 method_onSinkDisconnected, 261 env->NewStringUTF(c_path)); 262 if (env->ExceptionCheck()) { 263 LOGE("VM Exception occurred in native function %s (%s:%d)", 264 __FUNCTION__, __FILE__, __LINE__); 265 } 266 } // else Java callback is triggered by signal in a2dp_event_filter 267 268 free(c_path); 269} 270 271static void onDisconnectSinkResult(DBusMessage *msg, void *user, void *natData) { 272 LOGV(__FUNCTION__); 273 274 char *c_path = (char *)user; 275 DBusError err; 276 JNIEnv *env; 277 278 if (nat->vm->GetEnv((void**)&env, nat->envVer) < 0) { 279 LOGE("%s: error finding Env for our VM\n", __FUNCTION__); 280 return; 281 } 282 283 dbus_error_init(&err); 284 285 LOGV("... path = %s", c_path); 286 if (dbus_set_error_from_message(&err, msg)) { 287 /* if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationFailed")) */ 288 LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); 289 if (strcmp(err.name, "org.bluez.Error.NotConnected") == 0) { 290 // we were already disconnected, so report disconnect 291 env->CallVoidMethod(nat->me, 292 method_onSinkDisconnected, 293 env->NewStringUTF(c_path)); 294 } else { 295 // Assume it is still connected 296 env->CallVoidMethod(nat->me, 297 method_onSinkConnected, 298 env->NewStringUTF(c_path)); 299 } 300 dbus_error_free(&err); 301 if (env->ExceptionCheck()) { 302 LOGE("VM Exception occurred in native function %s (%s:%d)", 303 __FUNCTION__, __FILE__, __LINE__); 304 } 305 } // else Java callback is triggered by signal in a2dp_event_filter 306 307 free(c_path); 308} 309 310DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) { 311 DBusError err; 312 313 if (!nat) { 314 LOGV("... skipping %s\n", __FUNCTION__); 315 LOGV("... ignored\n"); 316 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 317 } 318 319 dbus_error_init(&err); 320 321 if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) { 322 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 323 } 324 325 DBusHandlerResult result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 326 327 if (dbus_message_is_signal(msg, 328 "org.bluez.audio.Manager", 329 "HeadsetCreated")) { 330 char *c_path; 331 if (dbus_message_get_args(msg, &err, 332 DBUS_TYPE_STRING, &c_path, 333 DBUS_TYPE_INVALID)) { 334 LOGV("... path = %s", c_path); 335 env->CallVoidMethod(nat->me, 336 method_onHeadsetCreated, 337 env->NewStringUTF(c_path)); 338 } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); 339 result = DBUS_HANDLER_RESULT_HANDLED; 340 } else if (dbus_message_is_signal(msg, 341 "org.bluez.audio.Manager", 342 "HeadsetRemoved")) { 343 char *c_path; 344 if (dbus_message_get_args(msg, &err, 345 DBUS_TYPE_STRING, &c_path, 346 DBUS_TYPE_INVALID)) { 347 LOGV("... path = %s", c_path); 348 env->CallVoidMethod(nat->me, 349 method_onHeadsetRemoved, 350 env->NewStringUTF(c_path)); 351 } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); 352 result = DBUS_HANDLER_RESULT_HANDLED; 353 } else if (dbus_message_is_signal(msg, 354 "org.bluez.audio.Sink", 355 "Connected")) { 356 const char *c_path = dbus_message_get_path(msg); 357 LOGV("... path = %s", c_path); 358 env->CallVoidMethod(nat->me, 359 method_onSinkConnected, 360 env->NewStringUTF(c_path)); 361 result = DBUS_HANDLER_RESULT_HANDLED; 362 } else if (dbus_message_is_signal(msg, 363 "org.bluez.audio.Sink", 364 "Disconnected")) { 365 const char *c_path = dbus_message_get_path(msg); 366 LOGV("... path = %s", c_path); 367 env->CallVoidMethod(nat->me, 368 method_onSinkDisconnected, 369 env->NewStringUTF(c_path)); 370 result = DBUS_HANDLER_RESULT_HANDLED; 371 } else if (dbus_message_is_signal(msg, 372 "org.bluez.audio.Sink", 373 "Playing")) { 374 const char *c_path = dbus_message_get_path(msg); 375 LOGV("... path = %s", c_path); 376 env->CallVoidMethod(nat->me, 377 method_onSinkPlaying, 378 env->NewStringUTF(c_path)); 379 result = DBUS_HANDLER_RESULT_HANDLED; 380 } else if (dbus_message_is_signal(msg, 381 "org.bluez.audio.Sink", 382 "Stopped")) { 383 const char *c_path = dbus_message_get_path(msg); 384 LOGV("... path = %s", c_path); 385 env->CallVoidMethod(nat->me, 386 method_onSinkStopped, 387 env->NewStringUTF(c_path)); 388 result = DBUS_HANDLER_RESULT_HANDLED; 389 } 390 391 if (result == DBUS_HANDLER_RESULT_NOT_YET_HANDLED) { 392 LOGV("... ignored"); 393 } 394 if (env->ExceptionCheck()) { 395 LOGE("VM Exception occurred while handling %s.%s (%s) in %s," 396 " leaving for VM", 397 dbus_message_get_interface(msg), dbus_message_get_member(msg), 398 dbus_message_get_path(msg), __FUNCTION__); 399 } 400 401 return result; 402} 403#endif 404 405 406static JNINativeMethod sMethods[] = { 407 {"initNative", "()Z", (void *)initNative}, 408 {"cleanupNative", "()V", (void *)cleanupNative}, 409 410 /* Bluez audio 3.36 API */ 411 {"listHeadsetsNative", "()[Ljava/lang/String;", (void*)listHeadsetsNative}, 412 {"createHeadsetNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*)createHeadsetNative}, 413 {"removeHeadsetNative", "(Ljava/lang/String;)Z", (void*)removeHeadsetNative}, 414 {"getAddressNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getAddressNative}, 415 {"connectSinkNative", "(Ljava/lang/String;)Z", (void*)connectSinkNative}, 416 {"disconnectSinkNative", "(Ljava/lang/String;)Z", (void*)disconnectSinkNative}, 417 {"isSinkConnectedNative", "(Ljava/lang/String;)Z", (void*)isSinkConnectedNative}, 418}; 419 420int register_android_server_BluetoothA2dpService(JNIEnv *env) { 421 jclass clazz = env->FindClass("android/server/BluetoothA2dpService"); 422 if (clazz == NULL) { 423 LOGE("Can't find android/server/BluetoothA2dpService"); 424 return -1; 425 } 426 427#ifdef HAVE_BLUETOOTH 428 method_onHeadsetCreated = env->GetMethodID(clazz, "onHeadsetCreated", "(Ljava/lang/String;)V"); 429 method_onHeadsetRemoved = env->GetMethodID(clazz, "onHeadsetRemoved", "(Ljava/lang/String;)V"); 430 method_onSinkConnected = env->GetMethodID(clazz, "onSinkConnected", "(Ljava/lang/String;)V"); 431 method_onSinkDisconnected = env->GetMethodID(clazz, "onSinkDisconnected", "(Ljava/lang/String;)V"); 432 method_onSinkPlaying = env->GetMethodID(clazz, "onSinkPlaying", "(Ljava/lang/String;)V"); 433 method_onSinkStopped = env->GetMethodID(clazz, "onSinkStopped", "(Ljava/lang/String;)V"); 434#endif 435 436 return AndroidRuntime::registerNativeMethods(env, 437 "android/server/BluetoothA2dpService", sMethods, NELEM(sMethods)); 438} 439 440} /* namespace android */ 441