Git init
[framework/multimedia/pulseaudio.git] / src / modules / bluetooth / bluetooth-util.c
1 /***
2   This file is part of PulseAudio.
3
4   Copyright 2008-2009 Joao Paulo Rechi Vita
5
6   PulseAudio is free software; you can redistribute it and/or modify
7   it under the terms of the GNU Lesser General Public License as
8   published by the Free Software Foundation; either version 2.1 of the
9   License, or (at your option) any later version.
10
11   PulseAudio is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with PulseAudio; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19   USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <pulsecore/core-util.h>
27 #include <pulsecore/shared.h>
28 #include <pulsecore/dbus-shared.h>
29
30 #include "bluetooth-util.h"
31 #include "ipc.h"
32
33 #define HFP_AG_ENDPOINT "/MediaEndpoint/HFPAG"
34 #define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
35 #define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
36
37 #define ENDPOINT_INTROSPECT_XML                                         \
38     DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
39     "<node>"                                                            \
40     " <interface name=\"org.bluez.MediaEndpoint\">"                     \
41     "  <method name=\"SetConfiguration\">"                              \
42     "   <arg name=\"transport\" direction=\"in\" type=\"o\"/>"          \
43     "   <arg name=\"configuration\" direction=\"in\" type=\"ay\"/>"     \
44     "  </method>"                                                       \
45     "  <method name=\"SelectConfiguration\">"                           \
46     "   <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>"      \
47     "   <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>"    \
48     "  </method>"                                                       \
49     "  <method name=\"ClearConfiguration\">"                            \
50     "  </method>"                                                       \
51     "  <method name=\"Release\">"                                       \
52     "  </method>"                                                       \
53     " </interface>"                                                     \
54     " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
55     "  <method name=\"Introspect\">"                                    \
56     "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
57     "  </method>"                                                       \
58     " </interface>"                                                     \
59     "</node>"
60
61 #define MAX_BITPOOL 64
62 #define MIN_BIPOOL 2U
63
64 struct pa_bluetooth_discovery {
65     PA_REFCNT_DECLARE;
66
67     pa_core *core;
68     pa_dbus_connection *connection;
69     PA_LLIST_HEAD(pa_dbus_pending, pending);
70     pa_hashmap *devices;
71     pa_hook hook;
72     pa_bool_t filter_added;
73 };
74
75 #ifdef DBUS_TYPE_UNIX_FD
76 #undef DBUS_TYPE_UNIX_FD
77 #endif
78
79 static void get_properties_reply(DBusPendingCall *pending, void *userdata);
80 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func, void *call_data);
81
82 static pa_bt_audio_state_t pa_bt_audio_state_from_string(const char* value) {
83     pa_assert(value);
84
85     if (pa_streq(value, "disconnected"))
86         return PA_BT_AUDIO_STATE_DISCONNECTED;
87     else if (pa_streq(value, "connecting"))
88         return PA_BT_AUDIO_STATE_CONNECTING;
89     else if (pa_streq(value, "connected"))
90         return PA_BT_AUDIO_STATE_CONNECTED;
91     else if (pa_streq(value, "playing"))
92         return PA_BT_AUDIO_STATE_PLAYING;
93
94     return PA_BT_AUDIO_STATE_INVALID;
95 }
96
97 static pa_bluetooth_uuid *uuid_new(const char *uuid) {
98     pa_bluetooth_uuid *u;
99
100     u = pa_xnew(pa_bluetooth_uuid, 1);
101     u->uuid = pa_xstrdup(uuid);
102     PA_LLIST_INIT(pa_bluetooth_uuid, u);
103
104     return u;
105 }
106
107 static void uuid_free(pa_bluetooth_uuid *u) {
108     pa_assert(u);
109
110     pa_xfree(u->uuid);
111     pa_xfree(u);
112 }
113
114 static pa_bluetooth_device* device_new(const char *path) {
115     pa_bluetooth_device *d;
116
117     d = pa_xnew(pa_bluetooth_device, 1);
118
119     d->dead = FALSE;
120
121     d->device_info_valid = 0;
122
123     d->name = NULL;
124     d->path = pa_xstrdup(path);
125     d->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
126     d->paired = -1;
127     d->alias = NULL;
128     d->device_connected = -1;
129     PA_LLIST_HEAD_INIT(pa_bluetooth_uuid, d->uuids);
130     d->address = NULL;
131     d->class = -1;
132     d->trusted = -1;
133
134     d->audio_state = PA_BT_AUDIO_STATE_INVALID;
135     d->audio_sink_state = PA_BT_AUDIO_STATE_INVALID;
136     d->audio_source_state = PA_BT_AUDIO_STATE_INVALID;
137     d->headset_state = PA_BT_AUDIO_STATE_INVALID;
138     d->hfgw_state = PA_BT_AUDIO_STATE_INVALID;
139
140     return d;
141 }
142
143 static void transport_free(pa_bluetooth_transport *t) {
144     pa_assert(t);
145
146     pa_xfree(t->path);
147     pa_xfree(t->config);
148     pa_xfree(t);
149 }
150
151 static void device_free(pa_bluetooth_device *d) {
152     pa_bluetooth_uuid *u;
153     pa_bluetooth_transport *t;
154
155     pa_assert(d);
156
157     while ((t = pa_hashmap_steal_first(d->transports)))
158         transport_free(t);
159
160     pa_hashmap_free(d->transports, NULL, NULL);
161
162     while ((u = d->uuids)) {
163         PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u);
164         uuid_free(u);
165     }
166
167     pa_xfree(d->name);
168     pa_xfree(d->path);
169     pa_xfree(d->alias);
170     pa_xfree(d->address);
171     pa_xfree(d);
172 }
173
174 static pa_bool_t device_is_audio(pa_bluetooth_device *d) {
175     pa_assert(d);
176
177     return
178         d->device_info_valid && (d->hfgw_state != PA_BT_AUDIO_STATE_INVALID ||
179         (d->audio_state != PA_BT_AUDIO_STATE_INVALID &&
180          (d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID ||
181           d->audio_source_state != PA_BT_AUDIO_STATE_INVALID ||
182           d->headset_state != PA_BT_AUDIO_STATE_INVALID)));
183 }
184
185 static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) {
186     const char *key;
187     DBusMessageIter variant_i;
188
189     pa_assert(y);
190     pa_assert(d);
191     pa_assert(i);
192
193     if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
194         pa_log("Property name not a string.");
195         return -1;
196     }
197
198     dbus_message_iter_get_basic(i, &key);
199
200     if (!dbus_message_iter_next(i)) {
201         pa_log("Property value missing");
202         return -1;
203     }
204
205     if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
206         pa_log("Property value not a variant.");
207         return -1;
208     }
209
210     dbus_message_iter_recurse(i, &variant_i);
211
212 /*     pa_log_debug("Parsing property org.bluez.Device.%s", key); */
213
214     switch (dbus_message_iter_get_arg_type(&variant_i)) {
215
216         case DBUS_TYPE_STRING: {
217
218             const char *value;
219             dbus_message_iter_get_basic(&variant_i, &value);
220
221             if (pa_streq(key, "Name")) {
222                 pa_xfree(d->name);
223                 d->name = pa_xstrdup(value);
224             } else if (pa_streq(key, "Alias")) {
225                 pa_xfree(d->alias);
226                 d->alias = pa_xstrdup(value);
227             } else if (pa_streq(key, "Address")) {
228                 pa_xfree(d->address);
229                 d->address = pa_xstrdup(value);
230             }
231
232 /*             pa_log_debug("Value %s", value); */
233
234             break;
235         }
236
237         case DBUS_TYPE_BOOLEAN: {
238
239             dbus_bool_t value;
240             dbus_message_iter_get_basic(&variant_i, &value);
241
242             if (pa_streq(key, "Paired"))
243                 d->paired = !!value;
244             else if (pa_streq(key, "Connected"))
245                 d->device_connected = !!value;
246             else if (pa_streq(key, "Trusted"))
247                 d->trusted = !!value;
248
249 /*             pa_log_debug("Value %s", pa_yes_no(value)); */
250
251             break;
252         }
253
254         case DBUS_TYPE_UINT32: {
255
256             uint32_t value;
257             dbus_message_iter_get_basic(&variant_i, &value);
258
259             if (pa_streq(key, "Class"))
260                 d->class = (int) value;
261
262 /*             pa_log_debug("Value %u", (unsigned) value); */
263
264             break;
265         }
266
267         case DBUS_TYPE_ARRAY: {
268
269             DBusMessageIter ai;
270             dbus_message_iter_recurse(&variant_i, &ai);
271
272             if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING &&
273                 pa_streq(key, "UUIDs")) {
274
275                 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
276                     pa_bluetooth_uuid *node;
277                     const char *value;
278                     DBusMessage *m;
279
280                     dbus_message_iter_get_basic(&ai, &value);
281                     node = uuid_new(value);
282                     PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
283
284                     /* Vudentz said the interfaces are here when the UUIDs are announced */
285                     if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) {
286                         pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.HandsfreeGateway", "GetProperties"));
287                         send_and_add_to_pending(y, m, get_properties_reply, d);
288                     } else if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) {
289                         pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset", "GetProperties"));
290                         send_and_add_to_pending(y, m, get_properties_reply, d);
291                     } else if (strcasecmp(A2DP_SINK_UUID, value) == 0) {
292                         pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink", "GetProperties"));
293                         send_and_add_to_pending(y, m, get_properties_reply, d);
294                     } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) {
295                         pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource", "GetProperties"));
296                         send_and_add_to_pending(y, m, get_properties_reply, d);
297                     }
298
299                     /* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */
300                     pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
301                     send_and_add_to_pending(y, m, get_properties_reply, d);
302
303                     if (!dbus_message_iter_next(&ai))
304                         break;
305                 }
306             }
307
308             break;
309         }
310     }
311
312     return 0;
313 }
314
315 static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessageIter *i) {
316     const char *key;
317     DBusMessageIter variant_i;
318
319     pa_assert(u);
320     pa_assert(state);
321     pa_assert(i);
322
323     if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
324         pa_log("Property name not a string.");
325         return -1;
326     }
327
328     dbus_message_iter_get_basic(i, &key);
329
330     if (!dbus_message_iter_next(i)) {
331         pa_log("Property value missing");
332         return -1;
333     }
334
335     if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
336         pa_log("Property value not a variant.");
337         return -1;
338     }
339
340     dbus_message_iter_recurse(i, &variant_i);
341
342 /*     pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
343
344     switch (dbus_message_iter_get_arg_type(&variant_i)) {
345
346         case DBUS_TYPE_STRING: {
347
348             const char *value;
349             dbus_message_iter_get_basic(&variant_i, &value);
350
351             if (pa_streq(key, "State")) {
352                 *state = pa_bt_audio_state_from_string(value);
353                 pa_log_debug("dbus: property 'State' changed to value '%s'", value);
354             }
355
356             break;
357         }
358     }
359
360     return 0;
361 }
362
363 static void run_callback(pa_bluetooth_discovery *y, pa_bluetooth_device *d, pa_bool_t dead) {
364     pa_assert(y);
365     pa_assert(d);
366
367     if (!device_is_audio(d))
368         return;
369
370     d->dead = dead;
371     pa_hook_fire(&y->hook, d);
372 }
373
374 static void remove_all_devices(pa_bluetooth_discovery *y) {
375     pa_bluetooth_device *d;
376
377     pa_assert(y);
378
379     while ((d = pa_hashmap_steal_first(y->devices))) {
380         run_callback(y, d, TRUE);
381         device_free(d);
382     }
383 }
384
385 static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path) {
386     DBusMessage *m;
387     pa_bluetooth_device *d;
388
389     pa_assert(y);
390     pa_assert(path);
391
392     d = pa_hashmap_get(y->devices, path);
393     if (d)
394         return d;
395
396     d = device_new(path);
397
398     pa_hashmap_put(y->devices, d->path, d);
399
400     pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties"));
401     send_and_add_to_pending(y, m, get_properties_reply, d);
402
403     /* Before we read the other properties (Audio, AudioSink, AudioSource,
404      * Headset) we wait that the UUID is read */
405     return d;
406 }
407
408 static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
409     DBusMessage *r;
410     DBusMessageIter arg_i, element_i;
411     pa_dbus_pending *p;
412     pa_bluetooth_device *d;
413     pa_bluetooth_discovery *y;
414     int valid;
415
416     pa_assert_se(p = userdata);
417     pa_assert_se(y = p->context_data);
418     pa_assert_se(r = dbus_pending_call_steal_reply(pending));
419
420 /*     pa_log_debug("Got %s.GetProperties response for %s", */
421 /*                  dbus_message_get_interface(p->message), */
422 /*                  dbus_message_get_path(p->message)); */
423
424     /* We don't use p->call_data here right-away since the device
425      * might already be invalidated at this point */
426
427     if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(p->message))))
428         return;
429
430     pa_assert(p->call_data == d);
431
432     valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1;
433
434     if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties"))
435         d->device_info_valid = valid;
436
437     if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
438         pa_log_debug("Bluetooth daemon is apparently not available.");
439         remove_all_devices(y);
440         goto finish2;
441     }
442
443     if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
444
445         if (!dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD))
446             pa_log("Error from GetProperties reply: %s", dbus_message_get_error_name(r));
447
448         goto finish;
449     }
450
451     if (!dbus_message_iter_init(r, &arg_i)) {
452         pa_log("GetProperties reply has no arguments.");
453         goto finish;
454     }
455
456     if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
457         pa_log("GetProperties argument is not an array.");
458         goto finish;
459     }
460
461     dbus_message_iter_recurse(&arg_i, &element_i);
462     while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) {
463
464         if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
465             DBusMessageIter dict_i;
466
467             dbus_message_iter_recurse(&element_i, &dict_i);
468
469             if (dbus_message_has_interface(p->message, "org.bluez.Device")) {
470                 if (parse_device_property(y, d, &dict_i) < 0)
471                     goto finish;
472
473             } else if (dbus_message_has_interface(p->message, "org.bluez.Audio")) {
474                 if (parse_audio_property(y, &d->audio_state, &dict_i) < 0)
475                     goto finish;
476
477             } else if (dbus_message_has_interface(p->message, "org.bluez.Headset")) {
478                 if (parse_audio_property(y, &d->headset_state, &dict_i) < 0)
479                     goto finish;
480
481             }  else if (dbus_message_has_interface(p->message, "org.bluez.AudioSink")) {
482                 if (parse_audio_property(y, &d->audio_sink_state, &dict_i) < 0)
483                     goto finish;
484
485             }  else if (dbus_message_has_interface(p->message, "org.bluez.AudioSource")) {
486                 if (parse_audio_property(y, &d->audio_source_state, &dict_i) < 0)
487                     goto finish;
488
489             }  else if (dbus_message_has_interface(p->message, "org.bluez.HandsfreeGateway")) {
490                 if (parse_audio_property(y, &d->hfgw_state, &arg_i) < 0)
491                     goto finish;
492
493             }
494         }
495
496         if (!dbus_message_iter_next(&element_i))
497             break;
498     }
499
500 finish:
501     run_callback(y, d, FALSE);
502
503 finish2:
504     dbus_message_unref(r);
505
506     PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
507     pa_dbus_pending_free(p);
508 }
509
510 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func, void *call_data) {
511     pa_dbus_pending *p;
512     DBusPendingCall *call;
513
514     pa_assert(y);
515     pa_assert(m);
516
517     pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
518
519     p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, call_data);
520     PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);
521     dbus_pending_call_set_notify(call, func, p, NULL);
522
523     return p;
524 }
525
526 #ifdef DBUS_TYPE_UNIX_FD
527 static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
528     DBusError e;
529     DBusMessage *r;
530     pa_dbus_pending *p;
531     pa_bluetooth_discovery *y;
532     char *endpoint;
533
534     pa_assert(pending);
535
536     dbus_error_init(&e);
537
538     pa_assert_se(p = userdata);
539     pa_assert_se(y = p->context_data);
540     pa_assert_se(endpoint = p->call_data);
541     pa_assert_se(r = dbus_pending_call_steal_reply(pending));
542
543     if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
544         pa_log_debug("Bluetooth daemon is apparently not available.");
545         remove_all_devices(y);
546         goto finish;
547     }
548
549     if (dbus_message_is_error(r, PA_BLUETOOTH_ERROR_NOT_SUPPORTED)) {
550         pa_log_info("Couldn't register endpoint %s, because BlueZ is configured to disable the endpoint type.", endpoint);
551         goto finish;
552     }
553
554     if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
555         pa_log("Error from RegisterEndpoint reply: %s", dbus_message_get_error_name(r));
556         goto finish;
557     }
558
559 finish:
560     dbus_message_unref(r);
561
562     PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
563     pa_dbus_pending_free(p);
564
565     pa_xfree(endpoint);
566 }
567 #endif
568
569 static void list_devices_reply(DBusPendingCall *pending, void *userdata) {
570     DBusError e;
571     DBusMessage *r;
572     char **paths = NULL;
573     int num = -1;
574     pa_dbus_pending *p;
575     pa_bluetooth_discovery *y;
576
577     pa_assert(pending);
578
579     dbus_error_init(&e);
580
581     pa_assert_se(p = userdata);
582     pa_assert_se(y = p->context_data);
583     pa_assert_se(r = dbus_pending_call_steal_reply(pending));
584
585     if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
586         pa_log_debug("Bluetooth daemon is apparently not available.");
587         remove_all_devices(y);
588         goto finish;
589     }
590
591     if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
592         pa_log("Error from ListDevices reply: %s", dbus_message_get_error_name(r));
593         goto finish;
594     }
595
596     if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) {
597         pa_log("org.bluez.Adapter.ListDevices returned an error: '%s'\n", e.message);
598         dbus_error_free(&e);
599     } else {
600         int i;
601
602         for (i = 0; i < num; ++i)
603             found_device(y, paths[i]);
604     }
605
606 finish:
607     if (paths)
608         dbus_free_string_array(paths);
609
610     dbus_message_unref(r);
611
612     PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
613     pa_dbus_pending_free(p);
614 }
615
616 #ifdef DBUS_TYPE_UNIX_FD
617 static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
618     DBusMessage *m;
619     DBusMessageIter i, d;
620     uint8_t codec = 0;
621
622     pa_log_debug("Registering %s on adapter %s.", endpoint, path);
623
624     pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Media", "RegisterEndpoint"));
625
626     dbus_message_iter_init_append(m, &i);
627
628     dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint);
629
630     dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
631                                     DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
632                                     &d);
633
634     pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid);
635
636     pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec);
637
638     if (pa_streq(uuid, HFP_AG_UUID)) {
639         uint8_t capability = 0;
640         uint8_t *caps = &capability;
641         pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &caps, 1);
642     } else {
643         sbc_capabilities_raw_t capabilities;
644         uint8_t *caps = (uint8_t *) &capabilities;
645
646         capabilities.channel_mode = BT_A2DP_CHANNEL_MODE_MONO | BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL |
647                                     BT_A2DP_CHANNEL_MODE_STEREO | BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
648         capabilities.frequency = BT_SBC_SAMPLING_FREQ_16000 | BT_SBC_SAMPLING_FREQ_32000 |
649                                  BT_SBC_SAMPLING_FREQ_44100 | BT_SBC_SAMPLING_FREQ_48000;
650         capabilities.allocation_method = BT_A2DP_ALLOCATION_SNR | BT_A2DP_ALLOCATION_LOUDNESS;
651         capabilities.subbands = BT_A2DP_SUBBANDS_4 | BT_A2DP_SUBBANDS_8;
652         capabilities.block_length = BT_A2DP_BLOCK_LENGTH_4 | BT_A2DP_BLOCK_LENGTH_8 |
653                                     BT_A2DP_BLOCK_LENGTH_12 | BT_A2DP_BLOCK_LENGTH_16;
654         capabilities.min_bitpool = MIN_BITPOOL;
655         capabilities.max_bitpool = MAX_BITPOOL;
656
657         pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &caps, sizeof(capabilities));
658     }
659
660     dbus_message_iter_close_container(&i, &d);
661
662     send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
663 }
664 #endif
665
666 static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
667     DBusMessage *m;
668
669     pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "ListDevices"));
670     send_and_add_to_pending(y, m, list_devices_reply, NULL);
671
672 #ifdef DBUS_TYPE_UNIX_FD
673     register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID);
674     register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID);
675     register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID);
676 #endif
677 }
678
679 static void list_adapters_reply(DBusPendingCall *pending, void *userdata) {
680     DBusError e;
681     DBusMessage *r;
682     char **paths = NULL;
683     int num = -1;
684     pa_dbus_pending *p;
685     pa_bluetooth_discovery *y;
686
687     pa_assert(pending);
688
689     dbus_error_init(&e);
690
691     pa_assert_se(p = userdata);
692     pa_assert_se(y = p->context_data);
693     pa_assert_se(r = dbus_pending_call_steal_reply(pending));
694
695     if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
696         pa_log_debug("Bluetooth daemon is apparently not available.");
697         remove_all_devices(y);
698         goto finish;
699     }
700
701     if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
702         pa_log("Error from ListAdapters reply: %s", dbus_message_get_error_name(r));
703         goto finish;
704     }
705
706     if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) {
707         pa_log("org.bluez.Manager.ListAdapters returned an error: %s", e.message);
708         dbus_error_free(&e);
709     } else {
710         int i;
711
712         for (i = 0; i < num; ++i)
713             found_adapter(y, paths[i]);
714     }
715
716 finish:
717     if (paths)
718         dbus_free_string_array(paths);
719
720     dbus_message_unref(r);
721
722     PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
723     pa_dbus_pending_free(p);
724 }
725
726 static void list_adapters(pa_bluetooth_discovery *y) {
727     DBusMessage *m;
728     pa_assert(y);
729
730     pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "ListAdapters"));
731     send_and_add_to_pending(y, m, list_adapters_reply, NULL);
732 }
733
734 int pa_bluetooth_transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *i)
735 {
736     const char *key;
737     DBusMessageIter variant_i;
738
739     if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
740         pa_log("Property name not a string.");
741         return -1;
742     }
743
744     dbus_message_iter_get_basic(i, &key);
745
746     if (!dbus_message_iter_next(i))  {
747         pa_log("Property value missing");
748         return -1;
749     }
750
751     if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
752         pa_log("Property value not a variant.");
753         return -1;
754     }
755
756     dbus_message_iter_recurse(i, &variant_i);
757
758     switch (dbus_message_iter_get_arg_type(&variant_i)) {
759
760         case DBUS_TYPE_BOOLEAN: {
761
762             pa_bool_t *value;
763             dbus_message_iter_get_basic(&variant_i, &value);
764
765             if (pa_streq(key, "NREC"))
766                 t->nrec = value;
767
768             break;
769          }
770     }
771
772     return 0;
773 }
774
775 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
776     DBusError err;
777     pa_bluetooth_discovery *y;
778
779     pa_assert(bus);
780     pa_assert(m);
781
782     pa_assert_se(y = userdata);
783
784     dbus_error_init(&err);
785
786     pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
787             dbus_message_get_interface(m),
788             dbus_message_get_path(m),
789             dbus_message_get_member(m));
790
791     if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) {
792         const char *path;
793         pa_bluetooth_device *d;
794
795         if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
796             pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err.message);
797             goto fail;
798         }
799
800         pa_log_debug("Device %s removed", path);
801
802         if ((d = pa_hashmap_remove(y->devices, path))) {
803             run_callback(y, d, TRUE);
804             device_free(d);
805         }
806
807         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
808
809     } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceCreated")) {
810         const char *path;
811
812         if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
813             pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err.message);
814             goto fail;
815         }
816
817         pa_log_debug("Device %s created", path);
818
819         found_device(y, path);
820         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
821
822     } else if (dbus_message_is_signal(m, "org.bluez.Manager", "AdapterAdded")) {
823         const char *path;
824
825         if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
826             pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err.message);
827             goto fail;
828         }
829
830         pa_log_debug("Adapter %s created", path);
831
832         found_adapter(y, path);
833         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
834
835     } else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") ||
836                dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
837                dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") ||
838                dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged") ||
839                dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged") ||
840                dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
841
842         pa_bluetooth_device *d;
843
844         if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
845             DBusMessageIter arg_i;
846
847             if (!dbus_message_iter_init(m, &arg_i)) {
848                 pa_log("Failed to parse PropertyChanged: %s", err.message);
849                 goto fail;
850             }
851
852             if (dbus_message_has_interface(m, "org.bluez.Device")) {
853                 if (parse_device_property(y, d, &arg_i) < 0)
854                     goto fail;
855
856             } else if (dbus_message_has_interface(m, "org.bluez.Audio")) {
857                 if (parse_audio_property(y, &d->audio_state, &arg_i) < 0)
858                     goto fail;
859
860             } else if (dbus_message_has_interface(m, "org.bluez.Headset")) {
861                 if (parse_audio_property(y, &d->headset_state, &arg_i) < 0)
862                     goto fail;
863
864             }  else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) {
865                 if (parse_audio_property(y, &d->audio_sink_state, &arg_i) < 0)
866                     goto fail;
867
868             }  else if (dbus_message_has_interface(m, "org.bluez.AudioSource")) {
869                 if (parse_audio_property(y, &d->audio_source_state, &arg_i) < 0)
870                     goto fail;
871
872             }  else if (dbus_message_has_interface(m, "org.bluez.HandsfreeGateway")) {
873                 if (parse_audio_property(y, &d->hfgw_state, &arg_i) < 0)
874                     goto fail;
875             }
876
877             run_callback(y, d, FALSE);
878         }
879
880         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
881
882     } else if (dbus_message_is_signal(m, "org.bluez.Device", "DisconnectRequested")) {
883         pa_bluetooth_device *d;
884
885         if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
886             /* Device will disconnect in 2 sec */
887             d->audio_state = PA_BT_AUDIO_STATE_DISCONNECTED;
888             d->audio_sink_state = PA_BT_AUDIO_STATE_DISCONNECTED;
889             d->audio_source_state = PA_BT_AUDIO_STATE_DISCONNECTED;
890             d->headset_state = PA_BT_AUDIO_STATE_DISCONNECTED;
891             d->hfgw_state = PA_BT_AUDIO_STATE_DISCONNECTED;
892
893             run_callback(y, d, FALSE);
894         }
895
896         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
897
898     } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
899         const char *name, *old_owner, *new_owner;
900
901         if (!dbus_message_get_args(m, &err,
902                                    DBUS_TYPE_STRING, &name,
903                                    DBUS_TYPE_STRING, &old_owner,
904                                    DBUS_TYPE_STRING, &new_owner,
905                                    DBUS_TYPE_INVALID)) {
906             pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
907             goto fail;
908         }
909
910         if (pa_streq(name, "org.bluez")) {
911             if (old_owner && *old_owner) {
912                 pa_log_debug("Bluetooth daemon disappeared.");
913                 remove_all_devices(y);
914             }
915
916             if (new_owner && *new_owner) {
917                 pa_log_debug("Bluetooth daemon appeared.");
918                 list_adapters(y);
919             }
920         }
921
922         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
923     } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) {
924         pa_bluetooth_device *d;
925         pa_bluetooth_transport *t;
926         void *state = NULL;
927         DBusMessageIter arg_i;
928
929         while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
930             if ((t = pa_hashmap_get(d->transports, dbus_message_get_path(m))))
931                 break;
932
933         if (!t)
934             goto fail;
935
936         if (!dbus_message_iter_init(m, &arg_i)) {
937             pa_log("Failed to parse PropertyChanged: %s", err.message);
938             goto fail;
939         }
940
941         if (pa_bluetooth_transport_parse_property(t, &arg_i) < 0)
942             goto fail;
943
944         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
945     }
946
947 fail:
948     dbus_error_free(&err);
949
950     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
951 }
952
953 const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) {
954     pa_bluetooth_device *d;
955     void *state = NULL;
956
957     pa_assert(y);
958     pa_assert(PA_REFCNT_VALUE(y) > 0);
959     pa_assert(address);
960
961     if (!pa_hook_is_firing(&y->hook))
962         pa_bluetooth_discovery_sync(y);
963
964     while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
965         if (pa_streq(d->address, address))
966             return device_is_audio(d) ? d : NULL;
967
968     return NULL;
969 }
970
971 const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) {
972     pa_bluetooth_device *d;
973
974     pa_assert(y);
975     pa_assert(PA_REFCNT_VALUE(y) > 0);
976     pa_assert(path);
977
978     if (!pa_hook_is_firing(&y->hook))
979         pa_bluetooth_discovery_sync(y);
980
981     if ((d = pa_hashmap_get(y->devices, path)))
982         if (device_is_audio(d))
983             return d;
984
985     return NULL;
986 }
987
988 const pa_bluetooth_transport* pa_bluetooth_discovery_get_transport(pa_bluetooth_discovery *y, const char *path) {
989     pa_bluetooth_device *d;
990     pa_bluetooth_transport *t;
991     void *state = NULL;
992
993     pa_assert(y);
994     pa_assert(PA_REFCNT_VALUE(y) > 0);
995     pa_assert(path);
996
997     while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
998         if ((t = pa_hashmap_get(d->transports, path)))
999             return t;
1000
1001     return NULL;
1002 }
1003
1004 const pa_bluetooth_transport* pa_bluetooth_device_get_transport(const pa_bluetooth_device *d, enum profile profile) {
1005     pa_bluetooth_transport *t;
1006     void *state = NULL;
1007
1008     pa_assert(d);
1009
1010     while ((t = pa_hashmap_iterate(d->transports, &state, NULL)))
1011         if (t->profile == profile)
1012             return t;
1013
1014     return NULL;
1015 }
1016
1017 int pa_bluetooth_transport_acquire(const pa_bluetooth_transport *t, const char *accesstype, size_t *imtu, size_t *omtu) {
1018     DBusMessage *m, *r;
1019     DBusError err;
1020     int ret;
1021     uint16_t i, o;
1022
1023     pa_assert(t);
1024     pa_assert(t->y);
1025
1026     dbus_error_init(&err);
1027
1028     pa_assert_se(m = dbus_message_new_method_call("org.bluez", t->path, "org.bluez.MediaTransport", "Acquire"));
1029     pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
1030     r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->y->connection), m, -1, &err);
1031
1032     if (dbus_error_is_set(&err) || !r) {
1033         pa_log("Failed to acquire transport fd: %s", err.message);
1034         dbus_error_free(&err);
1035         return -1;
1036     }
1037
1038 #ifdef DBUS_TYPE_UNIX_FD
1039     if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_UINT16, &i, DBUS_TYPE_UINT16, &o, DBUS_TYPE_INVALID)) {
1040         pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err.message);
1041         ret = -1;
1042         dbus_error_free(&err);
1043         goto fail;
1044     }
1045 #endif
1046
1047     if (imtu)
1048         *imtu = i;
1049
1050     if (omtu)
1051         *omtu = o;
1052
1053 #ifdef DBUS_TYPE_UNIX_FD
1054 fail:
1055 #endif
1056     dbus_message_unref(r);
1057     return ret;
1058 }
1059
1060 void pa_bluetooth_transport_release(const pa_bluetooth_transport *t, const char *accesstype) {
1061     DBusMessage *m;
1062     DBusError err;
1063
1064     pa_assert(t);
1065     pa_assert(t->y);
1066
1067     dbus_error_init(&err);
1068
1069     pa_assert_se(m = dbus_message_new_method_call("org.bluez", t->path, "org.bluez.MediaTransport", "Release"));
1070     pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
1071     dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->y->connection), m, -1, &err);
1072
1073     if (dbus_error_is_set(&err)) {
1074         pa_log("Failed to release transport %s: %s", t->path, err.message);
1075         dbus_error_free(&err);
1076     } else
1077         pa_log_info("Transport %s released", t->path);
1078 }
1079
1080 static int setup_dbus(pa_bluetooth_discovery *y) {
1081     DBusError err;
1082
1083     dbus_error_init(&err);
1084
1085     y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err);
1086
1087     if (dbus_error_is_set(&err) || !y->connection) {
1088         pa_log("Failed to get D-Bus connection: %s", err.message);
1089         dbus_error_free(&err);
1090         return -1;
1091     }
1092
1093     return 0;
1094 }
1095
1096 #ifdef DBUS_TYPE_UNIX_FD
1097 static pa_bluetooth_transport *transport_new(pa_bluetooth_discovery *y, const char *path, enum profile p, const uint8_t *config, int size) {
1098     pa_bluetooth_transport *t;
1099
1100     t = pa_xnew0(pa_bluetooth_transport, 1);
1101     t->y = y;
1102     t->path = pa_xstrdup(path);
1103     t->profile = p;
1104     t->config_size = size;
1105
1106     if (size > 0) {
1107         t->config = pa_xnew(uint8_t, size);
1108         memcpy(t->config, config, size);
1109     }
1110
1111     return t;
1112 }
1113
1114 static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
1115     pa_bluetooth_discovery *y = userdata;
1116     pa_bluetooth_device *d;
1117     pa_bluetooth_transport *t;
1118     const char *path, *dev_path = NULL, *uuid = NULL;
1119     uint8_t *config = NULL;
1120     int size = 0;
1121     pa_bool_t nrec;
1122     enum profile p;
1123     DBusMessageIter args, props;
1124     DBusMessage *r;
1125
1126     dbus_message_iter_init(m, &args);
1127
1128     dbus_message_iter_get_basic(&args, &path);
1129     if (!dbus_message_iter_next(&args))
1130         goto fail;
1131
1132     dbus_message_iter_recurse(&args, &props);
1133     if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
1134         goto fail;
1135
1136     /* Read transport properties */
1137     while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) {
1138         const char *key;
1139         DBusMessageIter value, entry;
1140         int var;
1141
1142         dbus_message_iter_recurse(&props, &entry);
1143         dbus_message_iter_get_basic(&entry, &key);
1144
1145         dbus_message_iter_next(&entry);
1146         dbus_message_iter_recurse(&entry, &value);
1147
1148         var = dbus_message_iter_get_arg_type(&value);
1149         if (strcasecmp(key, "UUID") == 0) {
1150             if (var != DBUS_TYPE_STRING)
1151                 goto fail;
1152             dbus_message_iter_get_basic(&value, &uuid);
1153         } else if (strcasecmp(key, "Device") == 0) {
1154             if (var != DBUS_TYPE_OBJECT_PATH)
1155                 goto fail;
1156             dbus_message_iter_get_basic(&value, &dev_path);
1157         } else if (strcasecmp(key, "NREC") == 0) {
1158             if (var != DBUS_TYPE_BOOLEAN)
1159                 goto fail;
1160             dbus_message_iter_get_basic(&value, &nrec);
1161         } else if (strcasecmp(key, "Configuration") == 0) {
1162             DBusMessageIter array;
1163             if (var != DBUS_TYPE_ARRAY)
1164                 goto fail;
1165             dbus_message_iter_recurse(&value, &array);
1166             dbus_message_iter_get_fixed_array(&array, &config, &size);
1167         }
1168
1169         dbus_message_iter_next(&props);
1170     }
1171
1172     d = found_device(y, dev_path);
1173     if (!d)
1174         goto fail;
1175
1176     if (dbus_message_has_path(m, HFP_AG_ENDPOINT))
1177         p = PROFILE_HSP;
1178     else if (dbus_message_has_path(m, A2DP_SOURCE_ENDPOINT))
1179         p = PROFILE_A2DP;
1180     else
1181         p = PROFILE_A2DP_SOURCE;
1182
1183     t = transport_new(y, path, p, config, size);
1184     if (nrec)
1185         t->nrec = nrec;
1186     pa_hashmap_put(d->transports, t->path, t);
1187
1188     pa_log_debug("Transport %s profile %d available", t->path, t->profile);
1189
1190     pa_assert_se(r = dbus_message_new_method_return(m));
1191
1192     return r;
1193
1194 fail:
1195     pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments");
1196     pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1197                                                         "Unable to set configuration")));
1198     return r;
1199 }
1200
1201 static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
1202     pa_bluetooth_discovery *y = userdata;
1203     pa_bluetooth_device *d;
1204     pa_bluetooth_transport *t;
1205     void *state = NULL;
1206     DBusMessage *r;
1207     DBusError e;
1208     const char *path;
1209
1210     dbus_error_init(&e);
1211
1212     if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
1213         pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e.message);
1214         dbus_error_free(&e);
1215         goto fail;
1216     }
1217
1218     while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) {
1219         if ((t = pa_hashmap_get(d->transports, path))) {
1220             pa_log_debug("Clearing transport %s profile %d", t->path, t->profile);
1221             pa_hashmap_remove(d->transports, t->path);
1222             transport_free(t);
1223             break;
1224         }
1225     }
1226
1227     pa_assert_se(r = dbus_message_new_method_return(m));
1228
1229     return r;
1230
1231 fail:
1232     pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1233                                                         "Unable to clear configuration")));
1234     return r;
1235 }
1236
1237 static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
1238
1239     switch (freq) {
1240         case BT_SBC_SAMPLING_FREQ_16000:
1241         case BT_SBC_SAMPLING_FREQ_32000:
1242             return 53;
1243
1244         case BT_SBC_SAMPLING_FREQ_44100:
1245
1246             switch (mode) {
1247                 case BT_A2DP_CHANNEL_MODE_MONO:
1248                 case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL:
1249                     return 31;
1250
1251                 case BT_A2DP_CHANNEL_MODE_STEREO:
1252                 case BT_A2DP_CHANNEL_MODE_JOINT_STEREO:
1253                     return 53;
1254
1255                 default:
1256                     pa_log_warn("Invalid channel mode %u", mode);
1257                     return 53;
1258             }
1259
1260         case BT_SBC_SAMPLING_FREQ_48000:
1261
1262             switch (mode) {
1263                 case BT_A2DP_CHANNEL_MODE_MONO:
1264                 case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL:
1265                     return 29;
1266
1267                 case BT_A2DP_CHANNEL_MODE_STEREO:
1268                 case BT_A2DP_CHANNEL_MODE_JOINT_STEREO:
1269                     return 51;
1270
1271                 default:
1272                     pa_log_warn("Invalid channel mode %u", mode);
1273                     return 51;
1274             }
1275
1276         default:
1277             pa_log_warn("Invalid sampling freq %u", freq);
1278             return 53;
1279     }
1280 }
1281
1282 static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
1283     pa_bluetooth_discovery *y = userdata;
1284     sbc_capabilities_raw_t *cap, config;
1285     uint8_t *pconf = (uint8_t *) &config;
1286     int i, size;
1287     DBusMessage *r;
1288     DBusError e;
1289
1290     static const struct {
1291         uint32_t rate;
1292         uint8_t cap;
1293     } freq_table[] = {
1294         { 16000U, BT_SBC_SAMPLING_FREQ_16000 },
1295         { 32000U, BT_SBC_SAMPLING_FREQ_32000 },
1296         { 44100U, BT_SBC_SAMPLING_FREQ_44100 },
1297         { 48000U, BT_SBC_SAMPLING_FREQ_48000 }
1298     };
1299
1300     dbus_error_init(&e);
1301
1302     if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
1303         pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message);
1304         dbus_error_free(&e);
1305         goto fail;
1306     }
1307
1308     if (dbus_message_has_path(m, HFP_AG_ENDPOINT))
1309         goto done;
1310
1311     pa_assert(size == sizeof(config));
1312
1313     memset(&config, 0, sizeof(config));
1314
1315     /* Find the lowest freq that is at least as high as the requested
1316      * sampling rate */
1317     for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
1318         if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
1319             config.frequency = freq_table[i].cap;
1320             break;
1321         }
1322
1323     if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
1324         for (--i; i >= 0; i--) {
1325             if (cap->frequency & freq_table[i].cap) {
1326                 config.frequency = freq_table[i].cap;
1327                 break;
1328             }
1329         }
1330
1331         if (i < 0) {
1332             pa_log("Not suitable sample rate");
1333             goto fail;
1334         }
1335     }
1336
1337     pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
1338
1339     if (y->core->default_sample_spec.channels <= 1) {
1340         if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
1341             config.channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
1342     }
1343
1344     if (y->core->default_sample_spec.channels >= 2) {
1345         if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO)
1346             config.channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
1347         else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO)
1348             config.channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
1349         else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL)
1350             config.channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
1351         else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
1352             config.channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
1353         } else {
1354             pa_log("No supported channel modes");
1355             goto fail;
1356         }
1357     }
1358
1359     if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16)
1360         config.block_length = BT_A2DP_BLOCK_LENGTH_16;
1361     else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12)
1362         config.block_length = BT_A2DP_BLOCK_LENGTH_12;
1363     else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8)
1364         config.block_length = BT_A2DP_BLOCK_LENGTH_8;
1365     else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4)
1366         config.block_length = BT_A2DP_BLOCK_LENGTH_4;
1367     else {
1368         pa_log_error("No supported block lengths");
1369         goto fail;
1370     }
1371
1372     if (cap->subbands & BT_A2DP_SUBBANDS_8)
1373         config.subbands = BT_A2DP_SUBBANDS_8;
1374     else if (cap->subbands & BT_A2DP_SUBBANDS_4)
1375         config.subbands = BT_A2DP_SUBBANDS_4;
1376     else {
1377         pa_log_error("No supported subbands");
1378         goto fail;
1379     }
1380
1381     if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS)
1382         config.allocation_method = BT_A2DP_ALLOCATION_LOUDNESS;
1383     else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR)
1384         config.allocation_method = BT_A2DP_ALLOCATION_SNR;
1385
1386     config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool);
1387     config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool);
1388
1389 done:
1390     pa_assert_se(r = dbus_message_new_method_return(m));
1391
1392     pa_assert_se(dbus_message_append_args(
1393                                      r,
1394                                      DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size,
1395                                      DBUS_TYPE_INVALID));
1396
1397     return r;
1398
1399 fail:
1400     pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1401                                                         "Unable to select configuration")));
1402     return r;
1403 }
1404
1405 static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
1406     struct pa_bluetooth_discovery *y = userdata;
1407     DBusMessage *r = NULL;
1408     DBusError e;
1409     const char *path;
1410
1411     pa_assert(y);
1412
1413     pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
1414             dbus_message_get_interface(m),
1415             dbus_message_get_path(m),
1416             dbus_message_get_member(m));
1417
1418     path = dbus_message_get_path(m);
1419     dbus_error_init(&e);
1420
1421     if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT))
1422         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1423
1424     if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1425         const char *xml = ENDPOINT_INTROSPECT_XML;
1426
1427         pa_assert_se(r = dbus_message_new_method_return(m));
1428         pa_assert_se(dbus_message_append_args(
1429                                  r,
1430                                  DBUS_TYPE_STRING, &xml,
1431                                  DBUS_TYPE_INVALID));
1432
1433     } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SetConfiguration")) {
1434         r = endpoint_set_configuration(c, m, userdata);
1435     } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SelectConfiguration")) {
1436         r = endpoint_select_configuration(c, m, userdata);
1437     } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "ClearConfiguration"))
1438         r = endpoint_clear_configuration(c, m, userdata);
1439     else
1440         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1441
1442     if (r) {
1443         pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
1444         dbus_message_unref(r);
1445     }
1446
1447     return DBUS_HANDLER_RESULT_HANDLED;
1448 }
1449 #endif /* DBUS_TYPE_UNIX_FD */
1450
1451 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
1452     DBusError err;
1453     pa_bluetooth_discovery *y;
1454 #ifdef DBUS_TYPE_UNIX_FD
1455     static const DBusObjectPathVTable vtable_endpoint = {
1456         .message_function = endpoint_handler,
1457     };
1458 #endif
1459
1460     pa_assert(c);
1461
1462     dbus_error_init(&err);
1463
1464     if ((y = pa_shared_get(c, "bluetooth-discovery")))
1465         return pa_bluetooth_discovery_ref(y);
1466
1467     y = pa_xnew0(pa_bluetooth_discovery, 1);
1468     PA_REFCNT_INIT(y);
1469     y->core = c;
1470     y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1471     PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
1472     pa_hook_init(&y->hook, y);
1473     pa_shared_set(c, "bluetooth-discovery", y);
1474
1475     if (setup_dbus(y) < 0)
1476         goto fail;
1477
1478     /* dynamic detection of bluetooth audio devices */
1479     if (!dbus_connection_add_filter(pa_dbus_connection_get(y->connection), filter_cb, y, NULL)) {
1480         pa_log_error("Failed to add filter function");
1481         goto fail;
1482     }
1483     y->filter_added = TRUE;
1484
1485     if (pa_dbus_add_matches(
1486                 pa_dbus_connection_get(y->connection), &err,
1487                 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
1488                 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1489                 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1490                 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1491                 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1492                 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'",
1493                 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1494                 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1495                 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1496                 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1497                 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1498                 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1499                 NULL) < 0) {
1500         pa_log("Failed to add D-Bus matches: %s", err.message);
1501         goto fail;
1502     }
1503
1504 #ifdef DBUS_TYPE_UNIX_FD
1505     pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT, &vtable_endpoint, y));
1506     pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y));
1507     pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT, &vtable_endpoint, y));
1508 #endif
1509
1510     list_adapters(y);
1511
1512     return y;
1513
1514 fail:
1515
1516     if (y)
1517         pa_bluetooth_discovery_unref(y);
1518
1519     dbus_error_free(&err);
1520
1521     return NULL;
1522 }
1523
1524 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
1525     pa_assert(y);
1526     pa_assert(PA_REFCNT_VALUE(y) > 0);
1527
1528     PA_REFCNT_INC(y);
1529
1530     return y;
1531 }
1532
1533 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
1534     pa_assert(y);
1535     pa_assert(PA_REFCNT_VALUE(y) > 0);
1536
1537     if (PA_REFCNT_DEC(y) > 0)
1538         return;
1539
1540     pa_dbus_free_pending_list(&y->pending);
1541
1542     if (y->devices) {
1543         remove_all_devices(y);
1544         pa_hashmap_free(y->devices, NULL, NULL);
1545     }
1546
1547     if (y->connection) {
1548 #ifdef DBUS_TYPE_UNIX_FD
1549         dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT);
1550         dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
1551         dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
1552 #endif
1553         pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
1554                                "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
1555                                "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1556                                "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
1557                                "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1558                                "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1559                                "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1560                                "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'",
1561                                "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1562                                "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1563                                "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1564                                "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1565                                "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1566                                "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1567                                NULL);
1568
1569         if (y->filter_added)
1570             dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
1571
1572         pa_dbus_connection_unref(y->connection);
1573     }
1574
1575     pa_hook_done(&y->hook);
1576
1577     if (y->core)
1578         pa_shared_remove(y->core, "bluetooth-discovery");
1579
1580     pa_xfree(y);
1581 }
1582
1583 void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *y) {
1584     pa_assert(y);
1585     pa_assert(PA_REFCNT_VALUE(y) > 0);
1586
1587     pa_dbus_sync_pending_list(&y->pending);
1588 }
1589
1590 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y) {
1591     pa_assert(y);
1592     pa_assert(PA_REFCNT_VALUE(y) > 0);
1593
1594     return &y->hook;
1595 }
1596
1597 const char*pa_bluetooth_get_form_factor(uint32_t class) {
1598     unsigned i;
1599     const char *r;
1600
1601     static const char * const table[] = {
1602         [1] = "headset",
1603         [2] = "hands-free",
1604         [4] = "microphone",
1605         [5] = "speaker",
1606         [6] = "headphone",
1607         [7] = "portable",
1608         [8] = "car",
1609         [10] = "hifi"
1610     };
1611
1612     if (((class >> 8) & 31) != 4)
1613         return NULL;
1614
1615     if ((i = (class >> 2) & 63) > PA_ELEMENTSOF(table))
1616         r =  NULL;
1617     else
1618         r = table[i];
1619
1620     if (!r)
1621         pa_log_debug("Unknown Bluetooth minor device class %u", i);
1622
1623     return r;
1624 }
1625
1626 char *pa_bluetooth_cleanup_name(const char *name) {
1627     char *t, *s, *d;
1628     pa_bool_t space = FALSE;
1629
1630     pa_assert(name);
1631
1632     while ((*name >= 1 && *name <= 32) || *name >= 127)
1633         name++;
1634
1635     t = pa_xstrdup(name);
1636
1637     for (s = d = t; *s; s++) {
1638
1639         if (*s <= 32 || *s >= 127 || *s == '_') {
1640             space = TRUE;
1641             continue;
1642         }
1643
1644         if (space) {
1645             *(d++) = ' ';
1646             space = FALSE;
1647         }
1648
1649         *(d++) = *s;
1650     }
1651
1652     *d = 0;
1653
1654     return t;
1655 }
1656
1657 pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
1658     pa_assert(uuid);
1659
1660     while (uuids) {
1661         if (strcasecmp(uuids->uuid, uuid) == 0)
1662             return TRUE;
1663
1664         uuids = uuids->next;
1665     }
1666
1667     return FALSE;
1668 }