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