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 37 namespace android { 38 39 #ifdef HAVE_BLUETOOTH 40 static jmethodID method_onSinkPropertyChanged; 41 static jmethodID method_onConnectSinkResult; 42 43 typedef struct { 44 JavaVM *vm; 45 int envVer; 46 DBusConnection *conn; 47 jobject me; // for callbacks to java 48 } native_data_t; 49 50 static native_data_t *nat = NULL; // global native data 51 static void onConnectSinkResult(DBusMessage *msg, void *user, void *n); 52 53 static Properties sink_properties[] = { 54 {"State", DBUS_TYPE_STRING}, 55 {"Connected", DBUS_TYPE_BOOLEAN}, 56 {"Playing", DBUS_TYPE_BOOLEAN}, 57 }; 58 #endif 59 60 /* Returns true on success (even if adapter is present but disabled). 61 * Return false if dbus is down, or another serious error (out of memory) 62 */ 63 static bool initNative(JNIEnv* env, jobject object) { 64 LOGV(__FUNCTION__); 65 #ifdef HAVE_BLUETOOTH 66 nat = (native_data_t *)calloc(1, sizeof(native_data_t)); 67 if (NULL == nat) { 68 LOGE("%s: out of memory!", __FUNCTION__); 69 return false; 70 } 71 env->GetJavaVM( &(nat->vm) ); 72 nat->envVer = env->GetVersion(); 73 nat->me = env->NewGlobalRef(object); 74 75 DBusError err; 76 dbus_error_init(&err); 77 dbus_threads_init_default(); 78 nat->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); 79 if (dbus_error_is_set(&err)) { 80 LOGE("Could not get onto the system bus: %s", err.message); 81 dbus_error_free(&err); 82 return false; 83 } 84 dbus_connection_set_exit_on_disconnect(nat->conn, FALSE); 85 #endif /*HAVE_BLUETOOTH*/ 86 return true; 87 } 88 89 static void cleanupNative(JNIEnv* env, jobject object) { 90 #ifdef HAVE_BLUETOOTH 91 LOGV(__FUNCTION__); 92 if (nat) { 93 dbus_connection_close(nat->conn); 94 env->DeleteGlobalRef(nat->me); 95 free(nat); 96 nat = NULL; 97 } 98 #endif 99 } 100 101 static jobjectArray getSinkPropertiesNative(JNIEnv *env, jobject object, 102 jstring path) { 103 #ifdef HAVE_BLUETOOTH 104 LOGV(__FUNCTION__); 105 if (nat) { 106 DBusMessage *msg, *reply; 107 DBusError err; 108 dbus_error_init(&err); 109 110 const char *c_path = env->GetStringUTFChars(path, NULL); 111 reply = dbus_func_args_timeout(env, 112 nat->conn, -1, c_path, 113 "org.bluez.AudioSink", "GetProperties", 114 DBUS_TYPE_INVALID); 115 env->ReleaseStringUTFChars(path, c_path); 116 if (!reply && dbus_error_is_set(&err)) { 117 LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply); 118 return NULL; 119 } else if (!reply) { 120 LOGE("DBus reply is NULL in function %s", __FUNCTION__); 121 return NULL; 122 } 123 DBusMessageIter iter; 124 if (dbus_message_iter_init(reply, &iter)) 125 return parse_properties(env, &iter, (Properties *)&sink_properties, 126 sizeof(sink_properties) / sizeof(Properties)); 127 } 128 #endif 129 return NULL; 130 } 131 132 133 static jboolean connectSinkNative(JNIEnv *env, jobject object, jstring path) { 134 #ifdef HAVE_BLUETOOTH 135 LOGV(__FUNCTION__); 136 if (nat) { 137 const char *c_path = env->GetStringUTFChars(path, NULL); 138 int len = env->GetStringLength(path) + 1; 139 char *context_path = (char *)calloc(len, sizeof(char)); 140 strlcpy(context_path, c_path, len); // for callback 141 142 bool ret = dbus_func_args_async(env, nat->conn, -1, onConnectSinkResult, context_path, 143 nat, c_path, "org.bluez.AudioSink", "Connect", 144 DBUS_TYPE_INVALID); 145 146 env->ReleaseStringUTFChars(path, c_path); 147 return ret ? JNI_TRUE : JNI_FALSE; 148 } 149 #endif 150 return JNI_FALSE; 151 } 152 153 static jboolean disconnectSinkNative(JNIEnv *env, jobject object, 154 jstring path) { 155 #ifdef HAVE_BLUETOOTH 156 LOGV(__FUNCTION__); 157 if (nat) { 158 const char *c_path = env->GetStringUTFChars(path, NULL); 159 160 bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat, 161 c_path, "org.bluez.AudioSink", "Disconnect", 162 DBUS_TYPE_INVALID); 163 164 env->ReleaseStringUTFChars(path, c_path); 165 return ret ? JNI_TRUE : JNI_FALSE; 166 } 167 #endif 168 return JNI_FALSE; 169 } 170 171 static jboolean suspendSinkNative(JNIEnv *env, jobject object, 172 jstring path) { 173 #ifdef HAVE_BLUETOOTH 174 LOGV(__FUNCTION__); 175 if (nat) { 176 const char *c_path = env->GetStringUTFChars(path, NULL); 177 bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat, 178 c_path, "org.bluez.audio.Sink", "Suspend", 179 DBUS_TYPE_INVALID); 180 env->ReleaseStringUTFChars(path, c_path); 181 return ret ? JNI_TRUE : JNI_FALSE; 182 } 183 #endif 184 return JNI_FALSE; 185 } 186 187 static jboolean resumeSinkNative(JNIEnv *env, jobject object, 188 jstring path) { 189 #ifdef HAVE_BLUETOOTH 190 LOGV(__FUNCTION__); 191 if (nat) { 192 const char *c_path = env->GetStringUTFChars(path, NULL); 193 bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat, 194 c_path, "org.bluez.audio.Sink", "Resume", 195 DBUS_TYPE_INVALID); 196 env->ReleaseStringUTFChars(path, c_path); 197 return ret ? JNI_TRUE : JNI_FALSE; 198 } 199 #endif 200 return JNI_FALSE; 201 } 202 203 static jboolean avrcpVolumeUpNative(JNIEnv *env, jobject object, 204 jstring path) { 205 #ifdef HAVE_BLUETOOTH 206 LOGV(__FUNCTION__); 207 if (nat) { 208 const char *c_path = env->GetStringUTFChars(path, NULL); 209 bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat, 210 c_path, "org.bluez.Control", "VolumeUp", 211 DBUS_TYPE_INVALID); 212 env->ReleaseStringUTFChars(path, c_path); 213 return ret ? JNI_TRUE : JNI_FALSE; 214 } 215 #endif 216 return JNI_FALSE; 217 } 218 219 static jboolean avrcpVolumeDownNative(JNIEnv *env, jobject object, 220 jstring path) { 221 #ifdef HAVE_BLUETOOTH 222 LOGV(__FUNCTION__); 223 if (nat) { 224 const char *c_path = env->GetStringUTFChars(path, NULL); 225 bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat, 226 c_path, "org.bluez.Control", "VolumeDown", 227 DBUS_TYPE_INVALID); 228 env->ReleaseStringUTFChars(path, c_path); 229 return ret ? JNI_TRUE : JNI_FALSE; 230 } 231 #endif 232 return JNI_FALSE; 233 } 234 235 #ifdef HAVE_BLUETOOTH 236 DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) { 237 DBusError err; 238 239 if (!nat) { 240 LOGV("... skipping %s\n", __FUNCTION__); 241 LOGV("... ignored\n"); 242 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 243 } 244 245 dbus_error_init(&err); 246 247 if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) { 248 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 249 } 250 251 DBusHandlerResult result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 252 253 if (dbus_message_is_signal(msg, "org.bluez.AudioSink", 254 "PropertyChanged")) { 255 jobjectArray str_array = 256 parse_property_change(env, msg, (Properties *)&sink_properties, 257 sizeof(sink_properties) / sizeof(Properties)); 258 const char *c_path = dbus_message_get_path(msg); 259 jstring path = env->NewStringUTF(c_path); 260 env->CallVoidMethod(nat->me, 261 method_onSinkPropertyChanged, 262 path, 263 str_array); 264 env->DeleteLocalRef(path); 265 result = DBUS_HANDLER_RESULT_HANDLED; 266 return result; 267 } else { 268 LOGV("... ignored"); 269 } 270 if (env->ExceptionCheck()) { 271 LOGE("VM Exception occurred while handling %s.%s (%s) in %s," 272 " leaving for VM", 273 dbus_message_get_interface(msg), dbus_message_get_member(msg), 274 dbus_message_get_path(msg), __FUNCTION__); 275 } 276 277 return result; 278 } 279 280 void onConnectSinkResult(DBusMessage *msg, void *user, void *n) { 281 LOGV(__FUNCTION__); 282 283 native_data_t *nat = (native_data_t *)n; 284 const char *path = (const char *)user; 285 DBusError err; 286 dbus_error_init(&err); 287 JNIEnv *env; 288 nat->vm->GetEnv((void**)&env, nat->envVer); 289 290 291 bool result = JNI_TRUE; 292 if (dbus_set_error_from_message(&err, msg)) { 293 LOG_AND_FREE_DBUS_ERROR(&err); 294 result = JNI_FALSE; 295 } 296 LOGV("... Device Path = %s, result = %d", path, result); 297 298 jstring jPath = env->NewStringUTF(path); 299 env->CallVoidMethod(nat->me, 300 method_onConnectSinkResult, 301 jPath, 302 result); 303 env->DeleteLocalRef(jPath); 304 free(user); 305 } 306 307 308 #endif 309 310 311 static JNINativeMethod sMethods[] = { 312 {"initNative", "()Z", (void *)initNative}, 313 {"cleanupNative", "()V", (void *)cleanupNative}, 314 315 /* Bluez audio 4.47 API */ 316 {"connectSinkNative", "(Ljava/lang/String;)Z", (void *)connectSinkNative}, 317 {"disconnectSinkNative", "(Ljava/lang/String;)Z", (void *)disconnectSinkNative}, 318 {"suspendSinkNative", "(Ljava/lang/String;)Z", (void*)suspendSinkNative}, 319 {"resumeSinkNative", "(Ljava/lang/String;)Z", (void*)resumeSinkNative}, 320 {"getSinkPropertiesNative", "(Ljava/lang/String;)[Ljava/lang/Object;", 321 (void *)getSinkPropertiesNative}, 322 {"avrcpVolumeUpNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeUpNative}, 323 {"avrcpVolumeDownNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeDownNative}, 324 }; 325 326 int register_android_server_BluetoothA2dpService(JNIEnv *env) { 327 jclass clazz = env->FindClass("android/server/BluetoothA2dpService"); 328 if (clazz == NULL) { 329 LOGE("Can't find android/server/BluetoothA2dpService"); 330 return -1; 331 } 332 333 #ifdef HAVE_BLUETOOTH 334 method_onSinkPropertyChanged = env->GetMethodID(clazz, "onSinkPropertyChanged", 335 "(Ljava/lang/String;[Ljava/lang/String;)V"); 336 method_onConnectSinkResult = env->GetMethodID(clazz, "onConnectSinkResult", 337 "(Ljava/lang/String;Z)V"); 338 #endif 339 340 return AndroidRuntime::registerNativeMethods(env, 341 "android/server/BluetoothA2dpService", sMethods, NELEM(sMethods)); 342 } 343 344 } /* namespace android */ 345