1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2008-2013 João Paulo Rechi Vita
5 Copyrigth 2018-2019 Pali Rohár <pali.rohar@gmail.com>
6
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as
9 published by the Free Software Foundation; either version 2.1 of the
10 License, or (at your option) any later version.
11
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <pulse/rtclock.h>
26 #include <pulse/timeval.h>
27 #include <pulse/xmalloc.h>
28
29 #include <pulsecore/core.h>
30 #include <pulsecore/core-util.h>
31 #include <pulsecore/dbus-shared.h>
32 #include <pulsecore/log.h>
33 #include <pulsecore/macro.h>
34 #include <pulsecore/refcnt.h>
35 #include <pulsecore/shared.h>
36
37 #include "a2dp-codec-util.h"
38 #include "a2dp-codecs.h"
39
40 #include "bluez5-util.h"
41
42 #define WAIT_FOR_PROFILES_TIMEOUT_USEC (3 * PA_USEC_PER_SEC)
43
44 #define BLUEZ_SERVICE "org.bluez"
45 #define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1"
46 #define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1"
47 #define BLUEZ_MEDIA_INTERFACE BLUEZ_SERVICE ".Media1"
48 #define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1"
49 #define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
50
51 #define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
52
53 #define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
54 #define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
55
56 #define ENDPOINT_INTROSPECT_XML \
57 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
58 "<node>" \
59 " <interface name=\"" BLUEZ_MEDIA_ENDPOINT_INTERFACE "\">" \
60 " <method name=\"SetConfiguration\">" \
61 " <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \
62 " <arg name=\"properties\" direction=\"in\" type=\"ay\"/>" \
63 " </method>" \
64 " <method name=\"SelectConfiguration\">" \
65 " <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>" \
66 " <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>" \
67 " </method>" \
68 " <method name=\"ClearConfiguration\">" \
69 " <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \
70 " </method>" \
71 " <method name=\"Release\">" \
72 " </method>" \
73 " </interface>" \
74 " <interface name=\"org.freedesktop.DBus.Introspectable\">" \
75 " <method name=\"Introspect\">" \
76 " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
77 " </method>" \
78 " </interface>" \
79 "</node>"
80
81 struct pa_bluetooth_discovery {
82 PA_REFCNT_DECLARE;
83
84 pa_core *core;
85 pa_dbus_connection *connection;
86 bool filter_added;
87 bool matches_added;
88 bool objects_listed;
89 pa_hook hooks[PA_BLUETOOTH_HOOK_MAX];
90 pa_hashmap *adapters;
91 pa_hashmap *devices;
92 pa_hashmap *transports;
93
94 int headset_backend;
95 pa_bluetooth_backend *ofono_backend, *native_backend;
96 PA_LLIST_HEAD(pa_dbus_pending, pending);
97 };
98
send_and_add_to_pending(pa_bluetooth_discovery * y,DBusMessage * m,DBusPendingCallNotifyFunction func,void * call_data)99 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m,
100 DBusPendingCallNotifyFunction func, void *call_data) {
101 pa_dbus_pending *p;
102 DBusPendingCall *call;
103
104 pa_assert(y);
105 pa_assert(m);
106
107 pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
108
109 p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, call_data);
110 PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);
111 dbus_pending_call_set_notify(call, func, p, NULL);
112
113 return p;
114 }
115
check_variant_property(DBusMessageIter * i)116 static const char *check_variant_property(DBusMessageIter *i) {
117 const char *key;
118
119 pa_assert(i);
120
121 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
122 pa_log_error("Property name not a string.");
123 return NULL;
124 }
125
126 dbus_message_iter_get_basic(i, &key);
127
128 if (!dbus_message_iter_next(i)) {
129 pa_log_error("Property value missing");
130 return NULL;
131 }
132
133 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
134 pa_log_error("Property value not a variant.");
135 return NULL;
136 }
137
138 return key;
139 }
140
pa_bluetooth_transport_new(pa_bluetooth_device * d,const char * owner,const char * path,pa_bluetooth_profile_t p,const uint8_t * config,size_t size)141 pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const char *owner, const char *path,
142 pa_bluetooth_profile_t p, const uint8_t *config, size_t size) {
143 pa_bluetooth_transport *t;
144
145 t = pa_xnew0(pa_bluetooth_transport, 1);
146 t->device = d;
147 t->owner = pa_xstrdup(owner);
148 t->path = pa_xstrdup(path);
149 t->profile = p;
150 t->config_size = size;
151
152 if (size > 0) {
153 t->config = pa_xnew(uint8_t, size);
154 memcpy(t->config, config, size);
155 }
156
157 return t;
158 }
159
transport_state_to_string(pa_bluetooth_transport_state_t state)160 static const char *transport_state_to_string(pa_bluetooth_transport_state_t state) {
161 switch(state) {
162 case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
163 return "disconnected";
164 case PA_BLUETOOTH_TRANSPORT_STATE_IDLE:
165 return "idle";
166 case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
167 return "playing";
168 }
169
170 return "invalid";
171 }
172
device_supports_profile(pa_bluetooth_device * device,pa_bluetooth_profile_t profile)173 static bool device_supports_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) {
174 switch (profile) {
175 case PA_BLUETOOTH_PROFILE_A2DP_SINK:
176 return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SINK);
177 case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
178 return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE);
179 case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
180 return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS)
181 || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS_ALT)
182 || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_HF);
183 case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
184 return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_AG)
185 || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_AG);
186 case PA_BLUETOOTH_PROFILE_OFF:
187 pa_assert_not_reached();
188 }
189
190 pa_assert_not_reached();
191 }
192
device_is_profile_connected(pa_bluetooth_device * device,pa_bluetooth_profile_t profile)193 static bool device_is_profile_connected(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) {
194 if (device->transports[profile] && device->transports[profile]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
195 return true;
196 else
197 return false;
198 }
199
device_count_disconnected_profiles(pa_bluetooth_device * device)200 static unsigned device_count_disconnected_profiles(pa_bluetooth_device *device) {
201 pa_bluetooth_profile_t profile;
202 unsigned count = 0;
203
204 for (profile = 0; profile < PA_BLUETOOTH_PROFILE_COUNT; profile++) {
205 if (!device_supports_profile(device, profile))
206 continue;
207
208 if (!device_is_profile_connected(device, profile))
209 count++;
210 }
211
212 return count;
213 }
214
device_stop_waiting_for_profiles(pa_bluetooth_device * device)215 static void device_stop_waiting_for_profiles(pa_bluetooth_device *device) {
216 if (!device->wait_for_profiles_timer)
217 return;
218
219 device->discovery->core->mainloop->time_free(device->wait_for_profiles_timer);
220 device->wait_for_profiles_timer = NULL;
221 }
222
wait_for_profiles_cb(pa_mainloop_api * api,pa_time_event * event,const struct timeval * tv,void * userdata)223 static void wait_for_profiles_cb(pa_mainloop_api *api, pa_time_event* event, const struct timeval *tv, void *userdata) {
224 pa_bluetooth_device *device = userdata;
225 pa_strbuf *buf;
226 pa_bluetooth_profile_t profile;
227 bool first = true;
228 char *profiles_str;
229
230 device_stop_waiting_for_profiles(device);
231
232 buf = pa_strbuf_new();
233
234 for (profile = 0; profile < PA_BLUETOOTH_PROFILE_COUNT; profile++) {
235 if (device_is_profile_connected(device, profile))
236 continue;
237
238 if (!device_supports_profile(device, profile))
239 continue;
240
241 if (first)
242 first = false;
243 else
244 pa_strbuf_puts(buf, ", ");
245
246 pa_strbuf_puts(buf, pa_bluetooth_profile_to_string(profile));
247 }
248
249 profiles_str = pa_strbuf_to_string_free(buf);
250 pa_log_debug("Timeout expired, and device %s still has disconnected profiles: %s",
251 device->path, profiles_str);
252 pa_xfree(profiles_str);
253 pa_hook_fire(&device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], device);
254 }
255
device_start_waiting_for_profiles(pa_bluetooth_device * device)256 static void device_start_waiting_for_profiles(pa_bluetooth_device *device) {
257 pa_assert(!device->wait_for_profiles_timer);
258 device->wait_for_profiles_timer = pa_core_rttime_new(device->discovery->core,
259 pa_rtclock_now() + WAIT_FOR_PROFILES_TIMEOUT_USEC,
260 wait_for_profiles_cb, device);
261 }
262
pa_bluetooth_transport_set_state(pa_bluetooth_transport * t,pa_bluetooth_transport_state_t state)263 void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_transport_state_t state) {
264 bool old_any_connected;
265 unsigned n_disconnected_profiles;
266 bool new_device_appeared;
267 bool device_disconnected;
268
269 pa_assert(t);
270
271 if (t->state == state)
272 return;
273
274 old_any_connected = pa_bluetooth_device_any_transport_connected(t->device);
275
276 pa_log_debug("Transport %s state: %s -> %s",
277 t->path, transport_state_to_string(t->state), transport_state_to_string(state));
278
279 t->state = state;
280
281 pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
282
283 /* If there are profiles that are expected to get connected soon (based
284 * on the UUID list), we wait for a bit before announcing the new
285 * device, so that all profiles have time to get connected before the
286 * card object is created. If we didn't wait, the card would always
287 * have only one profile marked as available in the initial state,
288 * which would prevent module-card-restore from restoring the initial
289 * profile properly. */
290
291 n_disconnected_profiles = device_count_disconnected_profiles(t->device);
292
293 new_device_appeared = !old_any_connected && pa_bluetooth_device_any_transport_connected(t->device);
294 device_disconnected = old_any_connected && !pa_bluetooth_device_any_transport_connected(t->device);
295
296 if (new_device_appeared) {
297 if (n_disconnected_profiles > 0)
298 device_start_waiting_for_profiles(t->device);
299 else
300 pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], t->device);
301 return;
302 }
303
304 if (device_disconnected) {
305 if (t->device->wait_for_profiles_timer) {
306 /* If the timer is still running when the device disconnects, we
307 * never sent the notification of the device getting connected, so
308 * we don't need to send a notification about the disconnection
309 * either. Let's just stop the timer. */
310 device_stop_waiting_for_profiles(t->device);
311 } else
312 pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], t->device);
313 return;
314 }
315
316 if (n_disconnected_profiles == 0 && t->device->wait_for_profiles_timer) {
317 /* All profiles are now connected, so we can stop the wait timer and
318 * send a notification of the new device. */
319 device_stop_waiting_for_profiles(t->device);
320 pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], t->device);
321 }
322 }
323
pa_bluetooth_transport_put(pa_bluetooth_transport * t)324 void pa_bluetooth_transport_put(pa_bluetooth_transport *t) {
325 pa_assert(t);
326
327 t->device->transports[t->profile] = t;
328 pa_assert_se(pa_hashmap_put(t->device->discovery->transports, t->path, t) >= 0);
329 pa_bluetooth_transport_set_state(t, PA_BLUETOOTH_TRANSPORT_STATE_IDLE);
330 }
331
pa_bluetooth_transport_unlink(pa_bluetooth_transport * t)332 void pa_bluetooth_transport_unlink(pa_bluetooth_transport *t) {
333 pa_assert(t);
334
335 pa_bluetooth_transport_set_state(t, PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED);
336 pa_hashmap_remove(t->device->discovery->transports, t->path);
337 t->device->transports[t->profile] = NULL;
338 }
339
pa_bluetooth_transport_free(pa_bluetooth_transport * t)340 void pa_bluetooth_transport_free(pa_bluetooth_transport *t) {
341 pa_assert(t);
342
343 if (t->destroy)
344 t->destroy(t);
345 pa_bluetooth_transport_unlink(t);
346
347 pa_xfree(t->owner);
348 pa_xfree(t->path);
349 pa_xfree(t->config);
350 pa_xfree(t);
351 }
352
bluez5_transport_acquire_cb(pa_bluetooth_transport * t,bool optional,size_t * imtu,size_t * omtu)353 static int bluez5_transport_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
354 DBusMessage *m, *r;
355 DBusError err;
356 int ret;
357 uint16_t i, o;
358 const char *method = optional ? "TryAcquire" : "Acquire";
359
360 pa_assert(t);
361 pa_assert(t->device);
362 pa_assert(t->device->discovery);
363
364 pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, BLUEZ_MEDIA_TRANSPORT_INTERFACE, method));
365
366 dbus_error_init(&err);
367
368 r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
369 dbus_message_unref(m);
370 m = NULL;
371 if (!r) {
372 if (optional && pa_streq(err.name, "org.bluez.Error.NotAvailable"))
373 pa_log_info("Failed optional acquire of unavailable transport %s", t->path);
374 else
375 pa_log_error("Transport %s() failed for transport %s (%s)", method, t->path, err.message);
376
377 dbus_error_free(&err);
378 return -1;
379 }
380
381 if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_UINT16, &i, DBUS_TYPE_UINT16, &o,
382 DBUS_TYPE_INVALID)) {
383 pa_log_error("Failed to parse %s() reply: %s", method, err.message);
384 dbus_error_free(&err);
385 ret = -1;
386 goto finish;
387 }
388
389 if (imtu)
390 *imtu = i;
391
392 if (omtu)
393 *omtu = o;
394
395 finish:
396 dbus_message_unref(r);
397 return ret;
398 }
399
bluez5_transport_release_cb(pa_bluetooth_transport * t)400 static void bluez5_transport_release_cb(pa_bluetooth_transport *t) {
401 DBusMessage *m, *r;
402 DBusError err;
403
404 pa_assert(t);
405 pa_assert(t->device);
406 pa_assert(t->device->discovery);
407
408 dbus_error_init(&err);
409
410 if (t->state <= PA_BLUETOOTH_TRANSPORT_STATE_IDLE) {
411 pa_log_info("Transport %s auto-released by BlueZ or already released", t->path);
412 return;
413 }
414
415 pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, BLUEZ_MEDIA_TRANSPORT_INTERFACE, "Release"));
416 r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
417 dbus_message_unref(m);
418 m = NULL;
419 if (r) {
420 dbus_message_unref(r);
421 r = NULL;
422 }
423
424 if (dbus_error_is_set(&err)) {
425 pa_log_error("Failed to release transport %s: %s", t->path, err.message);
426 dbus_error_free(&err);
427 } else
428 pa_log_info("Transport %s released", t->path);
429 }
430
pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device * d)431 bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
432 unsigned i;
433
434 pa_assert(d);
435
436 if (!d->valid)
437 return false;
438
439 for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
440 if (d->transports[i] && d->transports[i]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
441 return true;
442
443 return false;
444 }
445
transport_state_from_string(const char * value,pa_bluetooth_transport_state_t * state)446 static int transport_state_from_string(const char* value, pa_bluetooth_transport_state_t *state) {
447 pa_assert(value);
448 pa_assert(state);
449
450 if (pa_streq(value, "idle"))
451 *state = PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
452 else if (pa_streq(value, "pending") || pa_streq(value, "active"))
453 *state = PA_BLUETOOTH_TRANSPORT_STATE_PLAYING;
454 else
455 return -1;
456
457 return 0;
458 }
459
parse_transport_property(pa_bluetooth_transport * t,DBusMessageIter * i)460 static void parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter *i) {
461 const char *key;
462 DBusMessageIter variant_i;
463
464 key = check_variant_property(i);
465 if (key == NULL)
466 return;
467
468 dbus_message_iter_recurse(i, &variant_i);
469
470 switch (dbus_message_iter_get_arg_type(&variant_i)) {
471
472 case DBUS_TYPE_STRING: {
473
474 const char *value;
475 dbus_message_iter_get_basic(&variant_i, &value);
476
477 if (pa_streq(key, "State")) {
478 pa_bluetooth_transport_state_t state;
479
480 if (transport_state_from_string(value, &state) < 0) {
481 pa_log_error("Invalid state received: %s", value);
482 return;
483 }
484
485 pa_bluetooth_transport_set_state(t, state);
486 }
487
488 break;
489 }
490 }
491
492 return;
493 }
494
parse_transport_properties(pa_bluetooth_transport * t,DBusMessageIter * i)495 static int parse_transport_properties(pa_bluetooth_transport *t, DBusMessageIter *i) {
496 DBusMessageIter element_i;
497
498 dbus_message_iter_recurse(i, &element_i);
499
500 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
501 DBusMessageIter dict_i;
502
503 dbus_message_iter_recurse(&element_i, &dict_i);
504
505 parse_transport_property(t, &dict_i);
506
507 dbus_message_iter_next(&element_i);
508 }
509
510 return 0;
511 }
512
device_create(pa_bluetooth_discovery * y,const char * path)513 static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char *path) {
514 pa_bluetooth_device *d;
515
516 pa_assert(y);
517 pa_assert(path);
518
519 d = pa_xnew0(pa_bluetooth_device, 1);
520 d->discovery = y;
521 d->path = pa_xstrdup(path);
522 d->uuids = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
523
524 pa_hashmap_put(y->devices, d->path, d);
525
526 return d;
527 }
528
pa_bluetooth_discovery_get_device_by_path(pa_bluetooth_discovery * y,const char * path)529 pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_path(pa_bluetooth_discovery *y, const char *path) {
530 pa_bluetooth_device *d;
531
532 pa_assert(y);
533 pa_assert(PA_REFCNT_VALUE(y) > 0);
534 pa_assert(path);
535
536 if ((d = pa_hashmap_get(y->devices, path)) && d->valid)
537 return d;
538
539 return NULL;
540 }
541
pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_discovery * y,const char * remote,const char * local)542 pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_discovery *y, const char *remote, const char *local) {
543 pa_bluetooth_device *d;
544 void *state = NULL;
545
546 pa_assert(y);
547 pa_assert(PA_REFCNT_VALUE(y) > 0);
548 pa_assert(remote);
549 pa_assert(local);
550
551 while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
552 if (d->valid && pa_streq(d->address, remote) && pa_streq(d->adapter->address, local))
553 return d;
554
555 return NULL;
556 }
557
device_free(pa_bluetooth_device * d)558 static void device_free(pa_bluetooth_device *d) {
559 unsigned i;
560
561 pa_assert(d);
562
563 device_stop_waiting_for_profiles(d);
564
565 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_UNLINK], d);
566
567 for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
568 pa_bluetooth_transport *t;
569
570 if (!(t = d->transports[i]))
571 continue;
572
573 pa_bluetooth_transport_free(t);
574 }
575
576 if (d->uuids)
577 pa_hashmap_free(d->uuids);
578
579 pa_xfree(d->path);
580 pa_xfree(d->alias);
581 pa_xfree(d->address);
582 pa_xfree(d->adapter_path);
583 pa_xfree(d);
584 }
585
device_remove(pa_bluetooth_discovery * y,const char * path)586 static void device_remove(pa_bluetooth_discovery *y, const char *path) {
587 pa_bluetooth_device *d;
588
589 if (!(d = pa_hashmap_remove(y->devices, path)))
590 pa_log_warn("Unknown device removed %s", path);
591 else {
592 pa_log_debug("Device %s removed", path);
593 device_free(d);
594 }
595 }
596
device_set_valid(pa_bluetooth_device * device,bool valid)597 static void device_set_valid(pa_bluetooth_device *device, bool valid) {
598 bool old_any_connected;
599
600 pa_assert(device);
601
602 if (valid == device->valid)
603 return;
604
605 old_any_connected = pa_bluetooth_device_any_transport_connected(device);
606 device->valid = valid;
607
608 if (pa_bluetooth_device_any_transport_connected(device) != old_any_connected)
609 pa_hook_fire(&device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], device);
610 }
611
device_update_valid(pa_bluetooth_device * d)612 static void device_update_valid(pa_bluetooth_device *d) {
613 pa_assert(d);
614
615 if (!d->properties_received) {
616 pa_assert(!d->valid);
617 return;
618 }
619
620 /* Check if mandatory properties are set. */
621 if (!d->address || !d->adapter_path || !d->alias) {
622 device_set_valid(d, false);
623 return;
624 }
625
626 if (!d->adapter || !d->adapter->valid) {
627 device_set_valid(d, false);
628 return;
629 }
630
631 device_set_valid(d, true);
632 }
633
device_set_adapter(pa_bluetooth_device * device,pa_bluetooth_adapter * adapter)634 static void device_set_adapter(pa_bluetooth_device *device, pa_bluetooth_adapter *adapter) {
635 pa_assert(device);
636
637 if (adapter == device->adapter)
638 return;
639
640 device->adapter = adapter;
641
642 device_update_valid(device);
643 }
644
adapter_create(pa_bluetooth_discovery * y,const char * path)645 static pa_bluetooth_adapter* adapter_create(pa_bluetooth_discovery *y, const char *path) {
646 pa_bluetooth_adapter *a;
647
648 pa_assert(y);
649 pa_assert(path);
650
651 a = pa_xnew0(pa_bluetooth_adapter, 1);
652 a->discovery = y;
653 a->path = pa_xstrdup(path);
654
655 pa_hashmap_put(y->adapters, a->path, a);
656
657 return a;
658 }
659
adapter_free(pa_bluetooth_adapter * a)660 static void adapter_free(pa_bluetooth_adapter *a) {
661 pa_bluetooth_device *d;
662 void *state;
663
664 pa_assert(a);
665 pa_assert(a->discovery);
666
667 PA_HASHMAP_FOREACH(d, a->discovery->devices, state)
668 if (d->adapter == a)
669 device_set_adapter(d, NULL);
670
671 pa_xfree(a->path);
672 pa_xfree(a->address);
673 pa_xfree(a);
674 }
675
adapter_remove(pa_bluetooth_discovery * y,const char * path)676 static void adapter_remove(pa_bluetooth_discovery *y, const char *path) {
677 pa_bluetooth_adapter *a;
678
679 if (!(a = pa_hashmap_remove(y->adapters, path)))
680 pa_log_warn("Unknown adapter removed %s", path);
681 else {
682 pa_log_debug("Adapter %s removed", path);
683 adapter_free(a);
684 }
685 }
686
parse_device_property(pa_bluetooth_device * d,DBusMessageIter * i)687 static void parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i) {
688 const char *key;
689 DBusMessageIter variant_i;
690
691 pa_assert(d);
692
693 key = check_variant_property(i);
694 if (key == NULL) {
695 pa_log_error("Received invalid property for device %s", d->path);
696 return;
697 }
698
699 dbus_message_iter_recurse(i, &variant_i);
700
701 switch (dbus_message_iter_get_arg_type(&variant_i)) {
702
703 case DBUS_TYPE_STRING: {
704 const char *value;
705 dbus_message_iter_get_basic(&variant_i, &value);
706
707 if (pa_streq(key, "Alias")) {
708 pa_xfree(d->alias);
709 d->alias = pa_xstrdup(value);
710 pa_log_debug("%s: %s", key, value);
711 } else if (pa_streq(key, "Address")) {
712 if (d->properties_received) {
713 pa_log_warn("Device property 'Address' expected to be constant but changed for %s, ignoring", d->path);
714 return;
715 }
716
717 if (d->address) {
718 pa_log_warn("Device %s: Received a duplicate 'Address' property, ignoring", d->path);
719 return;
720 }
721
722 d->address = pa_xstrdup(value);
723 pa_log_debug("%s: %s", key, value);
724 }
725
726 break;
727 }
728
729 case DBUS_TYPE_OBJECT_PATH: {
730 const char *value;
731 dbus_message_iter_get_basic(&variant_i, &value);
732
733 if (pa_streq(key, "Adapter")) {
734
735 if (d->properties_received) {
736 pa_log_warn("Device property 'Adapter' expected to be constant but changed for %s, ignoring", d->path);
737 return;
738 }
739
740 if (d->adapter_path) {
741 pa_log_warn("Device %s: Received a duplicate 'Adapter' property, ignoring", d->path);
742 return;
743 }
744
745 d->adapter_path = pa_xstrdup(value);
746 pa_log_debug("%s: %s", key, value);
747 }
748
749 break;
750 }
751
752 case DBUS_TYPE_UINT32: {
753 uint32_t value;
754 dbus_message_iter_get_basic(&variant_i, &value);
755
756 if (pa_streq(key, "Class")) {
757 d->class_of_device = value;
758 pa_log_debug("%s: %d", key, value);
759 }
760
761 break;
762 }
763
764 case DBUS_TYPE_ARRAY: {
765 DBusMessageIter ai;
766 dbus_message_iter_recurse(&variant_i, &ai);
767
768 if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING && pa_streq(key, "UUIDs")) {
769 /* bluetoothd never removes UUIDs from a device object so we
770 * don't need to check for disappeared UUIDs here. */
771 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
772 const char *value;
773 char *uuid;
774
775 dbus_message_iter_get_basic(&ai, &value);
776
777 if (pa_hashmap_get(d->uuids, value)) {
778 dbus_message_iter_next(&ai);
779 continue;
780 }
781
782 uuid = pa_xstrdup(value);
783 pa_hashmap_put(d->uuids, uuid, uuid);
784
785 pa_log_debug("%s: %s", key, value);
786 dbus_message_iter_next(&ai);
787 }
788 }
789
790 break;
791 }
792 }
793 }
794
parse_device_properties(pa_bluetooth_device * d,DBusMessageIter * i)795 static void parse_device_properties(pa_bluetooth_device *d, DBusMessageIter *i) {
796 DBusMessageIter element_i;
797
798 dbus_message_iter_recurse(i, &element_i);
799
800 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
801 DBusMessageIter dict_i;
802
803 dbus_message_iter_recurse(&element_i, &dict_i);
804 parse_device_property(d, &dict_i);
805 dbus_message_iter_next(&element_i);
806 }
807
808 if (!d->properties_received) {
809 d->properties_received = true;
810 device_update_valid(d);
811
812 if (!d->address || !d->adapter_path || !d->alias)
813 pa_log_error("Non-optional information missing for device %s", d->path);
814 }
815 }
816
parse_adapter_properties(pa_bluetooth_adapter * a,DBusMessageIter * i,bool is_property_change)817 static void parse_adapter_properties(pa_bluetooth_adapter *a, DBusMessageIter *i, bool is_property_change) {
818 DBusMessageIter element_i;
819
820 pa_assert(a);
821
822 dbus_message_iter_recurse(i, &element_i);
823
824 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
825 DBusMessageIter dict_i, variant_i;
826 const char *key;
827
828 dbus_message_iter_recurse(&element_i, &dict_i);
829
830 key = check_variant_property(&dict_i);
831 if (key == NULL) {
832 pa_log_error("Received invalid property for adapter %s", a->path);
833 return;
834 }
835
836 dbus_message_iter_recurse(&dict_i, &variant_i);
837
838 if (dbus_message_iter_get_arg_type(&variant_i) == DBUS_TYPE_STRING && pa_streq(key, "Address")) {
839 const char *value;
840
841 if (is_property_change) {
842 pa_log_warn("Adapter property 'Address' expected to be constant but changed for %s, ignoring", a->path);
843 return;
844 }
845
846 if (a->address) {
847 pa_log_warn("Adapter %s received a duplicate 'Address' property, ignoring", a->path);
848 return;
849 }
850
851 dbus_message_iter_get_basic(&variant_i, &value);
852 a->address = pa_xstrdup(value);
853 a->valid = true;
854 }
855
856 dbus_message_iter_next(&element_i);
857 }
858 }
859
register_endpoint_reply(DBusPendingCall * pending,void * userdata)860 static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
861 DBusMessage *r;
862 pa_dbus_pending *p;
863 pa_bluetooth_discovery *y;
864 char *endpoint;
865
866 pa_assert(pending);
867 pa_assert_se(p = userdata);
868 pa_assert_se(y = p->context_data);
869 pa_assert_se(endpoint = p->call_data);
870 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
871
872 if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) {
873 pa_log_info("Couldn't register endpoint %s because it is disabled in BlueZ", endpoint);
874 goto finish;
875 }
876
877 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
878 pa_log_error(BLUEZ_MEDIA_INTERFACE ".RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r),
879 pa_dbus_get_error_message(r));
880 goto finish;
881 }
882
883 finish:
884 dbus_message_unref(r);
885
886 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
887 pa_dbus_pending_free(p);
888
889 pa_xfree(endpoint);
890 }
891
register_endpoint(pa_bluetooth_discovery * y,const pa_a2dp_codec * a2dp_codec,const char * path,const char * endpoint,const char * uuid)892 static void register_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_codec *a2dp_codec, const char *path, const char *endpoint, const char *uuid) {
893 DBusMessage *m;
894 DBusMessageIter i, d;
895 uint8_t capabilities[MAX_A2DP_CAPS_SIZE];
896 size_t capabilities_size;
897 uint8_t codec_id;
898
899 pa_log_debug("Registering %s on adapter %s", endpoint, path);
900
901 codec_id = a2dp_codec->id.codec_id;
902 capabilities_size = a2dp_codec->fill_capabilities(capabilities);
903 pa_assert(capabilities_size != 0);
904
905 pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, path, BLUEZ_MEDIA_INTERFACE, "RegisterEndpoint"));
906
907 dbus_message_iter_init_append(m, &i);
908 pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint));
909 dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
910 DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &d);
911 pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid);
912 pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec_id);
913 pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, capabilities_size);
914
915 dbus_message_iter_close_container(&i, &d);
916
917 send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
918 }
919
parse_interfaces_and_properties(pa_bluetooth_discovery * y,DBusMessageIter * dict_i)920 static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
921 DBusMessageIter element_i;
922 const char *path;
923 void *state;
924 pa_bluetooth_device *d;
925
926 pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_OBJECT_PATH);
927 dbus_message_iter_get_basic(dict_i, &path);
928
929 pa_assert_se(dbus_message_iter_next(dict_i));
930 pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_ARRAY);
931
932 dbus_message_iter_recurse(dict_i, &element_i);
933
934 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
935 DBusMessageIter iface_i;
936 const char *interface;
937
938 dbus_message_iter_recurse(&element_i, &iface_i);
939
940 pa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_STRING);
941 dbus_message_iter_get_basic(&iface_i, &interface);
942
943 pa_assert_se(dbus_message_iter_next(&iface_i));
944 pa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY);
945
946 if (pa_streq(interface, BLUEZ_ADAPTER_INTERFACE)) {
947
948 const pa_a2dp_codec *a2dp_codec_sbc;
949 pa_bluetooth_adapter *a;
950
951 if ((a = pa_hashmap_get(y->adapters, path))) {
952 pa_log_error("Found duplicated D-Bus path for adapter %s", path);
953 return;
954 } else
955 a = adapter_create(y, path);
956
957 pa_log_debug("Adapter %s found", path);
958
959 parse_adapter_properties(a, &iface_i, false);
960
961 if (!a->valid)
962 return;
963
964 /* Currently only one A2DP codec is supported, so register only SBC
965 * Support for multiple codecs needs to use a new Bluez API which
966 * pulseaudio does not implement yet, patches are waiting in queue */
967 a2dp_codec_sbc = pa_bluetooth_get_a2dp_codec("sbc");
968 pa_assert(a2dp_codec_sbc);
969 register_endpoint(y, a2dp_codec_sbc, path, A2DP_SINK_ENDPOINT "/sbc", PA_BLUETOOTH_UUID_A2DP_SINK);
970 register_endpoint(y, a2dp_codec_sbc, path, A2DP_SOURCE_ENDPOINT "/sbc", PA_BLUETOOTH_UUID_A2DP_SOURCE);
971
972 } else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
973
974 if ((d = pa_hashmap_get(y->devices, path))) {
975 if (d->properties_received) {
976 pa_log_error("Found duplicated D-Bus path for device %s", path);
977 return;
978 }
979 } else
980 d = device_create(y, path);
981
982 pa_log_debug("Device %s found", d->path);
983
984 parse_device_properties(d, &iface_i);
985
986 } else
987 pa_log_debug("Unknown interface %s found, skipping", interface);
988
989 dbus_message_iter_next(&element_i);
990 }
991
992 PA_HASHMAP_FOREACH(d, y->devices, state) {
993 if (d->properties_received && !d->tried_to_link_with_adapter) {
994 if (d->adapter_path) {
995 device_set_adapter(d, pa_hashmap_get(d->discovery->adapters, d->adapter_path));
996
997 if (!d->adapter)
998 pa_log("Device %s points to a nonexistent adapter %s.", d->path, d->adapter_path);
999 else if (!d->adapter->valid)
1000 pa_log("Device %s points to an invalid adapter %s.", d->path, d->adapter_path);
1001 }
1002
1003 d->tried_to_link_with_adapter = true;
1004 }
1005 }
1006
1007 return;
1008 }
1009
pa_bluetooth_discovery_set_ofono_running(pa_bluetooth_discovery * y,bool is_running)1010 void pa_bluetooth_discovery_set_ofono_running(pa_bluetooth_discovery *y, bool is_running) {
1011 pa_assert(y);
1012
1013 pa_log_debug("oFono is running: %s", pa_yes_no(is_running));
1014 if (y->headset_backend != HEADSET_BACKEND_AUTO)
1015 return;
1016
1017 /* If ofono starts running, all devices that might be connected to the HS role
1018 * need to be disconnected, so that the devices can be handled by ofono */
1019 if (is_running) {
1020 void *state;
1021 pa_bluetooth_device *d;
1022
1023 PA_HASHMAP_FOREACH(d, y->devices, state) {
1024 if (device_supports_profile(d, PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)) {
1025 DBusMessage *m;
1026
1027 pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, d->path, "org.bluez.Device1", "Disconnect"));
1028 dbus_message_set_no_reply(m, true);
1029 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), m, NULL));
1030 dbus_message_unref(m);
1031 }
1032 }
1033 }
1034
1035 pa_bluetooth_native_backend_enable_hs_role(y->native_backend, !is_running);
1036 }
1037
get_managed_objects_reply(DBusPendingCall * pending,void * userdata)1038 static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata) {
1039 pa_dbus_pending *p;
1040 pa_bluetooth_discovery *y;
1041 DBusMessage *r;
1042 DBusMessageIter arg_i, element_i;
1043
1044 pa_assert_se(p = userdata);
1045 pa_assert_se(y = p->context_data);
1046 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
1047
1048 if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
1049 pa_log_warn("BlueZ D-Bus ObjectManager not available");
1050 goto finish;
1051 }
1052
1053 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
1054 pa_log_error("GetManagedObjects() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
1055 goto finish;
1056 }
1057
1058 if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{oa{sa{sv}}}")) {
1059 pa_log_error("Invalid reply signature for GetManagedObjects()");
1060 goto finish;
1061 }
1062
1063 dbus_message_iter_recurse(&arg_i, &element_i);
1064 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
1065 DBusMessageIter dict_i;
1066
1067 dbus_message_iter_recurse(&element_i, &dict_i);
1068
1069 parse_interfaces_and_properties(y, &dict_i);
1070
1071 dbus_message_iter_next(&element_i);
1072 }
1073
1074 y->objects_listed = true;
1075
1076 if (!y->native_backend && y->headset_backend != HEADSET_BACKEND_OFONO)
1077 y->native_backend = pa_bluetooth_native_backend_new(y->core, y, (y->headset_backend == HEADSET_BACKEND_NATIVE));
1078 if (!y->ofono_backend && y->headset_backend != HEADSET_BACKEND_NATIVE)
1079 y->ofono_backend = pa_bluetooth_ofono_backend_new(y->core, y);
1080
1081 finish:
1082 dbus_message_unref(r);
1083
1084 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
1085 pa_dbus_pending_free(p);
1086 }
1087
get_managed_objects(pa_bluetooth_discovery * y)1088 static void get_managed_objects(pa_bluetooth_discovery *y) {
1089 DBusMessage *m;
1090
1091 pa_assert(y);
1092
1093 pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, "/", "org.freedesktop.DBus.ObjectManager",
1094 "GetManagedObjects"));
1095 send_and_add_to_pending(y, m, get_managed_objects_reply, NULL);
1096 }
1097
pa_bluetooth_discovery_hook(pa_bluetooth_discovery * y,pa_bluetooth_hook_t hook)1098 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) {
1099 pa_assert(y);
1100 pa_assert(PA_REFCNT_VALUE(y) > 0);
1101
1102 return &y->hooks[hook];
1103 }
1104
filter_cb(DBusConnection * bus,DBusMessage * m,void * userdata)1105 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
1106 pa_bluetooth_discovery *y;
1107 DBusError err;
1108
1109 pa_assert(bus);
1110 pa_assert(m);
1111 pa_assert_se(y = userdata);
1112
1113 dbus_error_init(&err);
1114
1115 if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
1116 const char *name, *old_owner, *new_owner;
1117
1118 if (!dbus_message_get_args(m, &err,
1119 DBUS_TYPE_STRING, &name,
1120 DBUS_TYPE_STRING, &old_owner,
1121 DBUS_TYPE_STRING, &new_owner,
1122 DBUS_TYPE_INVALID)) {
1123 pa_log_error("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
1124 goto fail;
1125 }
1126
1127 if (pa_streq(name, BLUEZ_SERVICE)) {
1128 if (old_owner && *old_owner) {
1129 pa_log_debug("Bluetooth daemon disappeared");
1130 pa_hashmap_remove_all(y->devices);
1131 pa_hashmap_remove_all(y->adapters);
1132 y->objects_listed = false;
1133 if (y->ofono_backend) {
1134 pa_bluetooth_ofono_backend_free(y->ofono_backend);
1135 y->ofono_backend = NULL;
1136 }
1137 if (y->native_backend) {
1138 pa_bluetooth_native_backend_free(y->native_backend);
1139 y->native_backend = NULL;
1140 }
1141 }
1142
1143 if (new_owner && *new_owner) {
1144 pa_log_debug("Bluetooth daemon appeared");
1145 get_managed_objects(y);
1146 }
1147 }
1148
1149 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1150 } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")) {
1151 DBusMessageIter arg_i;
1152
1153 if (!y->objects_listed)
1154 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
1155
1156 if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oa{sa{sv}}")) {
1157 pa_log_error("Invalid signature found in InterfacesAdded");
1158 goto fail;
1159 }
1160
1161 parse_interfaces_and_properties(y, &arg_i);
1162
1163 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1164 } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")) {
1165 const char *p;
1166 DBusMessageIter arg_i;
1167 DBusMessageIter element_i;
1168
1169 if (!y->objects_listed)
1170 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
1171
1172 if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oas")) {
1173 pa_log_error("Invalid signature found in InterfacesRemoved");
1174 goto fail;
1175 }
1176
1177 dbus_message_iter_get_basic(&arg_i, &p);
1178
1179 pa_assert_se(dbus_message_iter_next(&arg_i));
1180 pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
1181
1182 dbus_message_iter_recurse(&arg_i, &element_i);
1183
1184 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) {
1185 const char *iface;
1186
1187 dbus_message_iter_get_basic(&element_i, &iface);
1188
1189 if (pa_streq(iface, BLUEZ_DEVICE_INTERFACE))
1190 device_remove(y, p);
1191 else if (pa_streq(iface, BLUEZ_ADAPTER_INTERFACE))
1192 adapter_remove(y, p);
1193
1194 dbus_message_iter_next(&element_i);
1195 }
1196
1197 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1198
1199 } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.Properties", "PropertiesChanged")) {
1200 DBusMessageIter arg_i;
1201 const char *iface;
1202
1203 if (!y->objects_listed)
1204 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
1205
1206 if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "sa{sv}as")) {
1207 pa_log_error("Invalid signature found in PropertiesChanged");
1208 goto fail;
1209 }
1210
1211 dbus_message_iter_get_basic(&arg_i, &iface);
1212
1213 pa_assert_se(dbus_message_iter_next(&arg_i));
1214 pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
1215
1216 if (pa_streq(iface, BLUEZ_ADAPTER_INTERFACE)) {
1217 pa_bluetooth_adapter *a;
1218
1219 pa_log_debug("Properties changed in adapter %s", dbus_message_get_path(m));
1220
1221 if (!(a = pa_hashmap_get(y->adapters, dbus_message_get_path(m)))) {
1222 pa_log_warn("Properties changed in unknown adapter");
1223 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1224 }
1225
1226 parse_adapter_properties(a, &arg_i, true);
1227
1228 } else if (pa_streq(iface, BLUEZ_DEVICE_INTERFACE)) {
1229 pa_bluetooth_device *d;
1230
1231 pa_log_debug("Properties changed in device %s", dbus_message_get_path(m));
1232
1233 if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
1234 pa_log_warn("Properties changed in unknown device");
1235 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1236 }
1237
1238 if (!d->properties_received)
1239 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1240
1241 parse_device_properties(d, &arg_i);
1242 } else if (pa_streq(iface, BLUEZ_MEDIA_TRANSPORT_INTERFACE)) {
1243 pa_bluetooth_transport *t;
1244
1245 pa_log_debug("Properties changed in transport %s", dbus_message_get_path(m));
1246
1247 if (!(t = pa_hashmap_get(y->transports, dbus_message_get_path(m))))
1248 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1249
1250 parse_transport_properties(t, &arg_i);
1251 }
1252
1253 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1254 }
1255
1256 fail:
1257 dbus_error_free(&err);
1258
1259 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1260 }
1261
pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile)1262 const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
1263 switch(profile) {
1264 case PA_BLUETOOTH_PROFILE_A2DP_SINK:
1265 return "a2dp_sink";
1266 case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
1267 return "a2dp_source";
1268 case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
1269 return "headset_head_unit";
1270 case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
1271 return "headset_audio_gateway";
1272 case PA_BLUETOOTH_PROFILE_OFF:
1273 return "off";
1274 }
1275
1276 return NULL;
1277 }
1278
a2dp_endpoint_to_a2dp_codec(const char * endpoint)1279 static const pa_a2dp_codec *a2dp_endpoint_to_a2dp_codec(const char *endpoint) {
1280 const char *codec_name;
1281
1282 if (pa_startswith(endpoint, A2DP_SINK_ENDPOINT "/"))
1283 codec_name = endpoint + strlen(A2DP_SINK_ENDPOINT "/");
1284 else if (pa_startswith(endpoint, A2DP_SOURCE_ENDPOINT "/"))
1285 codec_name = endpoint + strlen(A2DP_SOURCE_ENDPOINT "/");
1286 else
1287 return NULL;
1288
1289 return pa_bluetooth_get_a2dp_codec(codec_name);
1290 }
1291
endpoint_set_configuration(DBusConnection * conn,DBusMessage * m,void * userdata)1292 static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
1293 pa_bluetooth_discovery *y = userdata;
1294 pa_bluetooth_device *d;
1295 pa_bluetooth_transport *t;
1296 const pa_a2dp_codec *a2dp_codec = NULL;
1297 const char *sender, *path, *endpoint_path, *dev_path = NULL, *uuid = NULL;
1298 const uint8_t *config = NULL;
1299 int size = 0;
1300 pa_bluetooth_profile_t p = PA_BLUETOOTH_PROFILE_OFF;
1301 DBusMessageIter args, props;
1302 DBusMessage *r;
1303
1304 if (!dbus_message_iter_init(m, &args) || !pa_streq(dbus_message_get_signature(m), "oa{sv}")) {
1305 pa_log_error("Invalid signature for method SetConfiguration()");
1306 goto fail2;
1307 }
1308
1309 dbus_message_iter_get_basic(&args, &path);
1310
1311 if (pa_hashmap_get(y->transports, path)) {
1312 pa_log_error("Endpoint SetConfiguration(): Transport %s is already configured.", path);
1313 goto fail2;
1314 }
1315
1316 pa_assert_se(dbus_message_iter_next(&args));
1317
1318 dbus_message_iter_recurse(&args, &props);
1319 if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
1320 goto fail;
1321
1322 endpoint_path = dbus_message_get_path(m);
1323
1324 /* Read transport properties */
1325 while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) {
1326 const char *key;
1327 DBusMessageIter value, entry;
1328 int var;
1329
1330 dbus_message_iter_recurse(&props, &entry);
1331 dbus_message_iter_get_basic(&entry, &key);
1332
1333 dbus_message_iter_next(&entry);
1334 dbus_message_iter_recurse(&entry, &value);
1335
1336 var = dbus_message_iter_get_arg_type(&value);
1337
1338 if (pa_streq(key, "UUID")) {
1339 if (var != DBUS_TYPE_STRING) {
1340 pa_log_error("Property %s of wrong type %c", key, (char)var);
1341 goto fail;
1342 }
1343
1344 dbus_message_iter_get_basic(&value, &uuid);
1345
1346 if (pa_startswith(endpoint_path, A2DP_SINK_ENDPOINT "/"))
1347 p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
1348 else if (pa_startswith(endpoint_path, A2DP_SOURCE_ENDPOINT "/"))
1349 p = PA_BLUETOOTH_PROFILE_A2DP_SINK;
1350
1351 if ((pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE) && p != PA_BLUETOOTH_PROFILE_A2DP_SINK) ||
1352 (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK) && p != PA_BLUETOOTH_PROFILE_A2DP_SOURCE)) {
1353 pa_log_error("UUID %s of transport %s incompatible with endpoint %s", uuid, path, endpoint_path);
1354 goto fail;
1355 }
1356 } else if (pa_streq(key, "Device")) {
1357 if (var != DBUS_TYPE_OBJECT_PATH) {
1358 pa_log_error("Property %s of wrong type %c", key, (char)var);
1359 goto fail;
1360 }
1361
1362 dbus_message_iter_get_basic(&value, &dev_path);
1363 } else if (pa_streq(key, "Configuration")) {
1364 DBusMessageIter array;
1365
1366 if (var != DBUS_TYPE_ARRAY) {
1367 pa_log_error("Property %s of wrong type %c", key, (char)var);
1368 goto fail;
1369 }
1370
1371 dbus_message_iter_recurse(&value, &array);
1372 var = dbus_message_iter_get_arg_type(&array);
1373 if (var != DBUS_TYPE_BYTE) {
1374 pa_log_error("%s is an array of wrong type %c", key, (char)var);
1375 goto fail;
1376 }
1377
1378 dbus_message_iter_get_fixed_array(&array, &config, &size);
1379
1380 a2dp_codec = a2dp_endpoint_to_a2dp_codec(endpoint_path);
1381 pa_assert(a2dp_codec);
1382
1383 if (!a2dp_codec->is_configuration_valid(config, size))
1384 goto fail;
1385 }
1386
1387 dbus_message_iter_next(&props);
1388 }
1389
1390 if (!a2dp_codec)
1391 goto fail2;
1392
1393 if ((d = pa_hashmap_get(y->devices, dev_path))) {
1394 if (!d->valid) {
1395 pa_log_error("Information about device %s is invalid", dev_path);
1396 goto fail2;
1397 }
1398 } else {
1399 /* InterfacesAdded signal is probably on its way, device_info_valid is kept as 0. */
1400 pa_log_warn("SetConfiguration() received for unknown device %s", dev_path);
1401 d = device_create(y, dev_path);
1402 }
1403
1404 if (d->transports[p] != NULL) {
1405 pa_log_error("Cannot configure transport %s because profile %s is already used", path, pa_bluetooth_profile_to_string(p));
1406 goto fail2;
1407 }
1408
1409 sender = dbus_message_get_sender(m);
1410
1411 pa_assert_se(r = dbus_message_new_method_return(m));
1412 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
1413 dbus_message_unref(r);
1414
1415 t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
1416 t->a2dp_codec = a2dp_codec;
1417 t->acquire = bluez5_transport_acquire_cb;
1418 t->release = bluez5_transport_release_cb;
1419 pa_bluetooth_transport_put(t);
1420
1421 pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
1422
1423 return NULL;
1424
1425 fail:
1426 pa_log_error("Endpoint SetConfiguration(): invalid arguments");
1427
1428 fail2:
1429 pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to set configuration"));
1430 return r;
1431 }
1432
endpoint_select_configuration(DBusConnection * conn,DBusMessage * m,void * userdata)1433 static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
1434 pa_bluetooth_discovery *y = userdata;
1435 const char *endpoint_path;
1436 uint8_t *cap;
1437 int size;
1438 const pa_a2dp_codec *a2dp_codec;
1439 uint8_t config[MAX_A2DP_CAPS_SIZE];
1440 uint8_t *config_ptr = config;
1441 size_t config_size;
1442 DBusMessage *r;
1443 DBusError err;
1444
1445 endpoint_path = dbus_message_get_path(m);
1446
1447 dbus_error_init(&err);
1448
1449 if (!dbus_message_get_args(m, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
1450 pa_log_error("Endpoint SelectConfiguration(): %s", err.message);
1451 dbus_error_free(&err);
1452 goto fail;
1453 }
1454
1455 a2dp_codec = a2dp_endpoint_to_a2dp_codec(endpoint_path);
1456 pa_assert(a2dp_codec);
1457
1458 config_size = a2dp_codec->fill_preferred_configuration(&y->core->default_sample_spec, cap, size, config);
1459 if (config_size == 0)
1460 goto fail;
1461
1462 pa_assert_se(r = dbus_message_new_method_return(m));
1463 pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &config_ptr, config_size, DBUS_TYPE_INVALID));
1464
1465 return r;
1466
1467 fail:
1468 pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to select configuration"));
1469 return r;
1470 }
1471
endpoint_clear_configuration(DBusConnection * conn,DBusMessage * m,void * userdata)1472 static DBusMessage *endpoint_clear_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
1473 pa_bluetooth_discovery *y = userdata;
1474 pa_bluetooth_transport *t;
1475 DBusMessage *r;
1476 DBusError err;
1477 const char *path;
1478
1479 dbus_error_init(&err);
1480
1481 if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
1482 pa_log_error("Endpoint ClearConfiguration(): %s", err.message);
1483 dbus_error_free(&err);
1484 goto fail;
1485 }
1486
1487 if ((t = pa_hashmap_get(y->transports, path))) {
1488 pa_log_debug("Clearing transport %s profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
1489 pa_bluetooth_transport_free(t);
1490 }
1491
1492 pa_assert_se(r = dbus_message_new_method_return(m));
1493
1494 return r;
1495
1496 fail:
1497 pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to clear configuration"));
1498 return r;
1499 }
1500
endpoint_release(DBusConnection * conn,DBusMessage * m,void * userdata)1501 static DBusMessage *endpoint_release(DBusConnection *conn, DBusMessage *m, void *userdata) {
1502 DBusMessage *r = NULL;
1503
1504 /* From doc/media-api.txt in bluez:
1505 *
1506 * This method gets called when the service daemon
1507 * unregisters the endpoint. An endpoint can use it to do
1508 * cleanup tasks. There is no need to unregister the
1509 * endpoint, because when this method gets called it has
1510 * already been unregistered.
1511 *
1512 * We don't have any cleanup to do. */
1513
1514 /* Reply only if requested. Generally bluetoothd doesn't request a reply
1515 * to the Release() call. Sending replies when not requested on the system
1516 * bus tends to cause errors in syslog from dbus-daemon, because it
1517 * doesn't let unexpected replies through, so it's important to have this
1518 * check here. */
1519 if (!dbus_message_get_no_reply(m))
1520 pa_assert_se(r = dbus_message_new_method_return(m));
1521
1522 return r;
1523 }
1524
endpoint_handler(DBusConnection * c,DBusMessage * m,void * userdata)1525 static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
1526 struct pa_bluetooth_discovery *y = userdata;
1527 DBusMessage *r = NULL;
1528 const char *path, *interface, *member;
1529
1530 pa_assert(y);
1531
1532 path = dbus_message_get_path(m);
1533 interface = dbus_message_get_interface(m);
1534 member = dbus_message_get_member(m);
1535
1536 pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
1537
1538 if (!a2dp_endpoint_to_a2dp_codec(path))
1539 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1540
1541 if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1542 const char *xml = ENDPOINT_INTROSPECT_XML;
1543
1544 pa_assert_se(r = dbus_message_new_method_return(m));
1545 pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
1546
1547 } else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"))
1548 r = endpoint_set_configuration(c, m, userdata);
1549 else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SelectConfiguration"))
1550 r = endpoint_select_configuration(c, m, userdata);
1551 else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "ClearConfiguration"))
1552 r = endpoint_clear_configuration(c, m, userdata);
1553 else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "Release"))
1554 r = endpoint_release(c, m, userdata);
1555 else
1556 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1557
1558 if (r) {
1559 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
1560 dbus_message_unref(r);
1561 }
1562
1563 return DBUS_HANDLER_RESULT_HANDLED;
1564 }
1565
endpoint_init(pa_bluetooth_discovery * y,const char * endpoint)1566 static void endpoint_init(pa_bluetooth_discovery *y, const char *endpoint) {
1567 static const DBusObjectPathVTable vtable_endpoint = {
1568 .message_function = endpoint_handler,
1569 };
1570
1571 pa_assert(y);
1572 pa_assert(endpoint);
1573
1574 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), endpoint,
1575 &vtable_endpoint, y));
1576 }
1577
endpoint_done(pa_bluetooth_discovery * y,const char * endpoint)1578 static void endpoint_done(pa_bluetooth_discovery *y, const char *endpoint) {
1579 pa_assert(y);
1580 pa_assert(endpoint);
1581
1582 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), endpoint);
1583 }
1584
pa_bluetooth_discovery_get(pa_core * c,int headset_backend)1585 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend) {
1586 pa_bluetooth_discovery *y;
1587 DBusError err;
1588 DBusConnection *conn;
1589 unsigned i, count;
1590 const pa_a2dp_codec *a2dp_codec;
1591 char *endpoint;
1592
1593 y = pa_xnew0(pa_bluetooth_discovery, 1);
1594 PA_REFCNT_INIT(y);
1595 y->core = c;
1596 y->headset_backend = headset_backend;
1597 y->adapters = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
1598 (pa_free_cb_t) adapter_free);
1599 y->devices = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
1600 (pa_free_cb_t) device_free);
1601 y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1602 PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
1603
1604 for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
1605 pa_hook_init(&y->hooks[i], y);
1606
1607 pa_shared_set(c, "bluetooth-discovery", y);
1608
1609 dbus_error_init(&err);
1610
1611 if (!(y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err))) {
1612 pa_log_error("Failed to get D-Bus connection: %s", err.message);
1613 goto fail;
1614 }
1615
1616 conn = pa_dbus_connection_get(y->connection);
1617
1618 /* dynamic detection of bluetooth audio devices */
1619 if (!dbus_connection_add_filter(conn, filter_cb, y, NULL)) {
1620 pa_log_error("Failed to add filter function");
1621 goto fail;
1622 }
1623 y->filter_added = true;
1624
1625 if (pa_dbus_add_matches(conn, &err,
1626 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
1627 ",arg0='" BLUEZ_SERVICE "'",
1628 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
1629 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',"
1630 "member='InterfacesRemoved'",
1631 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
1632 ",arg0='" BLUEZ_ADAPTER_INTERFACE "'",
1633 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
1634 ",arg0='" BLUEZ_DEVICE_INTERFACE "'",
1635 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
1636 ",arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'",
1637 NULL) < 0) {
1638 pa_log_error("Failed to add D-Bus matches: %s", err.message);
1639 goto fail;
1640 }
1641 y->matches_added = true;
1642
1643 count = pa_bluetooth_a2dp_codec_count();
1644 for (i = 0; i < count; i++) {
1645 a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
1646
1647 endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
1648 endpoint_init(y, endpoint);
1649 pa_xfree(endpoint);
1650
1651 endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
1652 endpoint_init(y, endpoint);
1653 pa_xfree(endpoint);
1654 }
1655
1656 get_managed_objects(y);
1657
1658 return y;
1659
1660 fail:
1661 pa_bluetooth_discovery_unref(y);
1662 dbus_error_free(&err);
1663
1664 return NULL;
1665 }
1666
pa_bluetooth_discovery_ref(pa_bluetooth_discovery * y)1667 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
1668 pa_assert(y);
1669 pa_assert(PA_REFCNT_VALUE(y) > 0);
1670
1671 PA_REFCNT_INC(y);
1672
1673 return y;
1674 }
1675
pa_bluetooth_discovery_unref(pa_bluetooth_discovery * y)1676 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
1677 unsigned i, count;
1678 const pa_a2dp_codec *a2dp_codec;
1679 char *endpoint;
1680
1681 pa_assert(y);
1682 pa_assert(PA_REFCNT_VALUE(y) > 0);
1683
1684 if (PA_REFCNT_DEC(y) > 0)
1685 return;
1686
1687 pa_dbus_free_pending_list(&y->pending);
1688
1689 if (y->ofono_backend)
1690 pa_bluetooth_ofono_backend_free(y->ofono_backend);
1691 if (y->native_backend)
1692 pa_bluetooth_native_backend_free(y->native_backend);
1693
1694 if (y->adapters)
1695 pa_hashmap_free(y->adapters);
1696
1697 if (y->devices)
1698 pa_hashmap_free(y->devices);
1699
1700 if (y->transports) {
1701 pa_assert(pa_hashmap_isempty(y->transports));
1702 pa_hashmap_free(y->transports);
1703 }
1704
1705 if (y->connection) {
1706
1707 if (y->matches_added)
1708 pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
1709 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',"
1710 "arg0='" BLUEZ_SERVICE "'",
1711 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',"
1712 "member='InterfacesAdded'",
1713 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',"
1714 "member='InterfacesRemoved'",
1715 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
1716 "member='PropertiesChanged',arg0='" BLUEZ_ADAPTER_INTERFACE "'",
1717 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
1718 "member='PropertiesChanged',arg0='" BLUEZ_DEVICE_INTERFACE "'",
1719 "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
1720 "member='PropertiesChanged',arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'",
1721 NULL);
1722
1723 if (y->filter_added)
1724 dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
1725
1726 count = pa_bluetooth_a2dp_codec_count();
1727 for (i = 0; i < count; i++) {
1728 a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
1729
1730 endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
1731 endpoint_done(y, endpoint);
1732 pa_xfree(endpoint);
1733
1734 endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
1735 endpoint_done(y, endpoint);
1736 pa_xfree(endpoint);
1737 }
1738
1739 pa_dbus_connection_unref(y->connection);
1740 }
1741
1742 pa_shared_remove(y->core, "bluetooth-discovery");
1743 pa_xfree(y);
1744 }
1745