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