binding: bluetooth-pbap: add scope-platform permission to config.xml.in
[apps/agl-service-bluetooth-pbap.git] / binding / bluetooth-pbap-binding.c
1 /*
2  * Copyright (C) 2018 Konsulko Group
3  * Author: Matt Porter <mporter@konsulko.com>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE
19 #include <errno.h>
20 #include <glib.h>
21 #include <json-c/json.h>
22 #include <math.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/time.h>
27 #include <time.h>
28
29 #define AFB_BINDING_VERSION 3
30 #include <afb/afb-binding.h>
31
32 #include "obex_client1_interface.h"
33 #include "obex_session1_interface.h"
34 #include "obex_transfer1_interface.h"
35 #include "obex_phonebookaccess1_interface.h"
36 #include "freedesktop_dbus_properties_interface.h"
37
38 #include "bluetooth-pbap-common.h"
39
40 static GDBusObjectManager *obj_manager;
41 static OrgBluezObexClient1 *client;
42 static OrgBluezObexSession1 *session;
43 static OrgBluezObexPhonebookAccess1 *phonebook;
44 static GHashTable *xfer_queue;
45 static GMutex xfer_queue_mutex;
46 static GHashTable *xfer_complete;
47 static GMutex xfer_complete_mutex;
48 static GCond xfer_complete_cond;
49 static GMutex connected_mutex;
50 static gboolean connected = FALSE;
51 static gchar *connected_address = NULL;
52 static afb_event_t status_event;
53
54 #define PBAP_UUID       "0000112f-0000-1000-8000-00805f9b34fb"
55
56 #define INTERNAL        "int"
57 #define SIM             "sim"
58 #define SIM1            SIM
59 #define SIM2            "sim2"
60
61 #define CONTACTS        "pb"
62 #define COMBINED        "cch"
63 #define INCOMING        "ich"
64 #define OUTGOING        "och"
65 #define MISSED          "mch"
66
67
68 static int update_or_insert(const char *key, const char *value)
69 {
70         json_object *query = json_object_new_object();
71         int ret;
72
73         json_object_object_add(query, "key", json_object_new_string(key));
74         json_object_object_add(query, "value", json_object_new_string(value));
75         json_object_get(query);
76
77         ret = afb_service_call_sync("persistence", "update", query, NULL, NULL, NULL);
78         if (!ret) {
79                 AFB_DEBUG("Updating persistence value '%s'", key);
80                 json_object_put(query);
81                 return 0;
82         }
83
84         ret = afb_service_call_sync("persistence", "write", query, NULL, NULL, NULL);
85         if (!ret) {
86                 AFB_DEBUG("Create persistence value '%s'", key);
87                 return 0;
88         }
89
90         return ret;
91 }
92
93 static int read_cached_value(const char *key, const char **data)
94 {
95         json_object *response, *query;
96         int ret;
97
98         query = json_object_new_object();
99         json_object_object_add(query, "key", json_object_new_string(key));
100
101         ret = afb_service_call_sync("persistence", "read", query, &response, NULL, NULL);
102         if (!ret) {
103                 json_object *val = NULL;
104
105                 json_object_object_get_ex(response, "value", &val);
106                 if (!val)
107                         return -EINVAL;
108
109                 *data = g_strdup(json_object_get_string(val));
110         }
111
112         json_object_get(response);
113
114         return ret;
115 }
116
117 static void on_interface_proxy_properties_changed(
118                 GDBusObjectManagerClient *manager,
119                 GDBusObjectProxy *object_proxy,
120                 GDBusProxy *interface_proxy,
121                 GVariant *changed_properties,
122                 const gchar *const *invalidated_properties,
123                 gpointer user_data)
124 {
125         GVariantIter iter;
126         const gchar *key;
127         GVariant *value;
128         gchar *filename;
129
130         const gchar *path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object_proxy));
131
132         if ((filename = g_hash_table_lookup(xfer_queue, path))) {
133                 g_variant_iter_init(&iter, changed_properties);
134                 while (g_variant_iter_next(&iter, "{&sv}", &key, &value)) {
135                         const gchar *val = NULL;
136
137                         if (g_strcmp0(key, "Status"))
138                                 continue;
139
140                         val = g_variant_get_string(value, NULL);
141
142                         if (g_strcmp0(val, "complete") && g_strcmp0(val, "error"))
143                                 continue;
144
145                         g_mutex_lock(&xfer_complete_mutex);
146                         g_hash_table_insert(xfer_complete, g_strdup(path),
147                                 g_strdup(!g_strcmp0(val, "complete") ? filename : ""));
148                         g_cond_signal(&xfer_complete_cond);
149                         g_mutex_unlock(&xfer_complete_mutex);
150                         g_mutex_lock(&xfer_queue_mutex);
151                         g_hash_table_remove(xfer_queue, path);
152                         g_mutex_unlock(&xfer_queue_mutex);
153                 }
154         }
155 }
156
157 static char *get_vcard_xfer(gchar *filename)
158 {
159         FILE *fp;
160         gchar *vcard_data;
161         size_t size, n;
162
163         fp = fopen(filename, "ro");
164         fseek(fp, 0L, SEEK_END);
165         size = ftell(fp);
166         vcard_data = calloc(1, size);
167         fseek(fp, 0L, SEEK_SET);
168         n = fread(vcard_data, 1, size, fp);
169         if (n != size) {
170                 AFB_ERROR("Read only %ld/%ld bytes from %s", n, size, filename);
171                 return NULL;
172         }
173
174         fclose(fp);
175         unlink(filename);
176
177         return vcard_data;
178 }
179
180 static void get_filename(gchar *filename)
181 {
182         struct tm* tm_info;;
183         struct timeval tv;
184         long ms;
185         gchar buffer[64];
186
187         gettimeofday(&tv, NULL);
188
189         ms = lrint((double)tv.tv_usec/1000.0);
190         if (ms >= 1000) {
191                 ms -=1000;
192                 tv.tv_sec++;
193         }
194         tm_info = localtime(&tv.tv_sec);
195
196         strftime(buffer, 26, "%Y%m%d%H%M%S", tm_info);
197
198         sprintf(filename, "/tmp/vcard-%s%03ld.dat", buffer, ms);
199 }
200
201 static gchar *pull_vcard(const gchar *handle)
202 {
203         GVariantBuilder *b;
204         GVariant *filter, *properties;
205         gchar filename[256];
206         gchar *tpath;
207
208         b = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
209         g_variant_builder_add(b, "{sv}", "Format", g_variant_new_string("vcard30"));
210         filter = g_variant_builder_end(b);
211
212         get_filename(filename);
213         org_bluez_obex_phonebook_access1_call_pull_sync(
214                         phonebook, handle, filename, filter, &tpath, &properties, NULL, NULL);
215
216         g_variant_builder_unref(b);
217         g_mutex_lock(&xfer_queue_mutex);
218         g_hash_table_insert(xfer_queue, g_strdup(tpath), g_strdup(filename));
219         g_mutex_unlock(&xfer_queue_mutex);
220
221         return tpath;
222 }
223
224 static json_object *get_vcard(const gchar *handle)
225 {
226         json_object *vcard;
227         gchar *tpath, *filename, *vcard_str = NULL;
228
229         tpath = pull_vcard(handle);
230
231         g_mutex_lock(&xfer_complete_mutex);
232         while (!(filename = g_hash_table_lookup(xfer_complete, tpath)))
233                 g_cond_wait(&xfer_complete_cond, &xfer_complete_mutex);
234
235         if (strlen(filename) > 0)
236                 vcard_str = get_vcard_xfer(filename);
237
238         g_hash_table_remove(xfer_complete, tpath);
239         g_free(filename);
240         g_mutex_unlock(&xfer_complete_mutex);
241
242         vcard = json_object_new_object();
243         json_object_object_add(vcard, "vcards", parse_vcards(vcard_str));
244         g_free(vcard_str);
245
246         return vcard;
247 }
248
249 static gchar *pull_vcards(int max_entries)
250 {
251         GVariantBuilder *b;
252         GVariant *filter, *properties;
253         gchar filename[256];
254         gchar *tpath;
255
256         b = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
257         g_variant_builder_add(b, "{sv}", "Format", g_variant_new_string("vcard30"));
258         g_variant_builder_add(b, "{sv}", "Order", g_variant_new_string("indexed"));
259         g_variant_builder_add(b, "{sv}", "Offset", g_variant_new_uint16(0));
260         if (max_entries >= 0)
261                 g_variant_builder_add(b, "{sv}", "MaxCount", g_variant_new_uint16((guint16)max_entries));
262         filter = g_variant_builder_end(b);
263
264         get_filename(filename);
265         org_bluez_obex_phonebook_access1_call_pull_all_sync(
266                         phonebook, filename, filter, &tpath, &properties, NULL, NULL);
267         g_variant_builder_unref(b);
268         g_mutex_lock(&xfer_queue_mutex);
269         g_hash_table_insert(xfer_queue, g_strdup(tpath), g_strdup(filename));
270         g_mutex_unlock(&xfer_queue_mutex);
271
272         return tpath;
273 }
274
275 static json_object *get_vcards(int max_entries)
276 {
277         json_object *vcards;
278         gchar *tpath, *filename, *vcards_str = NULL;
279
280         tpath = pull_vcards(max_entries);
281         g_mutex_lock(&xfer_complete_mutex);
282         while (!(filename = g_hash_table_lookup(xfer_complete, tpath)))
283                 g_cond_wait(&xfer_complete_cond, &xfer_complete_mutex);
284
285         vcards_str = get_vcard_xfer(filename);
286
287         g_hash_table_remove(xfer_complete, tpath);
288         g_mutex_unlock(&xfer_complete_mutex);
289
290         vcards = json_object_new_object();
291         json_object_object_add(vcards, "vcards", parse_vcards(vcards_str));
292         g_free(vcards_str);
293
294         return vcards;
295 }
296
297 static gboolean parse_list_parameter(afb_req_t request, gchar **list)
298 {
299         struct json_object *list_obj, *query;
300         const gchar *list_str;
301
302         query = afb_req_json(request);
303
304         if (json_object_object_get_ex(query, "list", &list_obj) == TRUE) {
305                 if (json_object_is_type(list_obj, json_type_string)) {
306                         list_str = json_object_get_string(list_obj);
307                         if (!g_strcmp0(list_str, COMBINED)) {
308                                 *list = COMBINED;
309                         } else if (!g_strcmp0(list_str, INCOMING)) {
310                                 *list = INCOMING;
311                         } else if (!g_strcmp0(list_str, OUTGOING)) {
312                                 *list = OUTGOING;
313                         } else if (!g_strcmp0(list_str, MISSED)) {
314                                 *list = MISSED;
315                         } else if (!g_strcmp0(list_str, CONTACTS)) {
316                                 *list = CONTACTS;
317                         } else {
318                                 afb_req_fail(request, "invalid list", NULL);
319                                 return FALSE;
320                         }
321                 } else {
322                         afb_req_fail(request, "list not string", NULL);
323                         return FALSE;
324                 }
325         } else {
326                 afb_req_fail(request, "no list", NULL);
327                 return FALSE;
328         }
329
330         return TRUE;
331 }
332
333 static gboolean parse_max_entries_parameter(afb_req_t request, int *max_entries)
334 {
335         struct json_object *max_obj, *query;
336
337         query = afb_req_json(request);
338
339         if (json_object_object_get_ex(query, "max_entries", &max_obj) == TRUE) {
340                 if (json_object_is_type(max_obj, json_type_int)) {
341                         *max_entries = json_object_get_int(max_obj);
342                         if ((*max_entries < 0) || (*max_entries > (2e16-1))) {
343                                 afb_req_fail(request, "max_entries out of range", NULL);
344                                 return FALSE;
345                         }
346                 } else {
347                         afb_req_fail(request, "max_entries not integer", NULL);
348                         return FALSE;
349                 }
350         }
351
352         return TRUE;
353 }
354
355 void contacts(afb_req_t request)
356 {
357         struct json_object *jresp;
358         const char *cached = NULL;
359
360         if (!connected) {
361                 afb_req_fail(request, "not connected", NULL);
362                 return;
363         }
364
365         if (!read_cached_value(connected_address, &cached)) {
366                 jresp = json_tokener_parse(cached);
367         } else {
368                 afb_req_fail(request, "no imported contacts", NULL);
369         }
370
371         afb_req_success(request, jresp, "contacts");
372 }
373
374 void import(afb_req_t request)
375 {
376         struct json_object *jresp;
377         int max_entries = -1;
378         gchar *address;
379
380         g_mutex_lock(&connected_mutex);
381         if (!connected) {
382                 afb_req_fail(request, "not connected", NULL);
383                 g_mutex_unlock(&connected_mutex);
384                 return;
385         }
386
387         address = g_strdup(connected_address);
388         g_mutex_unlock(&connected_mutex);
389
390         if (!parse_max_entries_parameter(request, &max_entries))
391                 return;
392
393         if (!org_bluez_obex_phonebook_access1_call_select_sync(
394                                 phonebook, INTERNAL, CONTACTS, NULL, NULL)) {
395                 afb_req_fail(request, "cannot import contacts", NULL);
396                 goto out_free;
397         }
398
399         jresp = get_vcards(max_entries);
400         update_or_insert(address,
401                 json_object_to_json_string_ext(jresp, JSON_C_TO_STRING_PLAIN));
402
403         afb_req_success(request, jresp, "contacts");
404
405 out_free:
406         g_free(address);
407 }
408
409 void entry(afb_req_t request)
410 {
411         struct json_object *handle_obj, *jresp, *query;
412         const gchar *handle;
413         gchar *list = NULL;
414
415         if (!connected) {
416                 afb_req_fail(request, "not connected", NULL);
417                 return;
418         }
419
420         query = afb_req_json(request);
421
422         if (json_object_object_get_ex(query, "handle", &handle_obj) == TRUE) {
423                 if (json_object_is_type(handle_obj, json_type_string)) {
424                         handle = json_object_get_string(handle_obj);
425                 } else {
426                         afb_req_fail(request, "handle not string", NULL);
427                         return;
428                 }
429         } else {
430                 afb_req_fail(request, "no handle", NULL);
431                 return;
432         }
433
434         if (!parse_list_parameter(request, &list))
435                 return;
436
437         org_bluez_obex_phonebook_access1_call_select_sync(
438                         phonebook, INTERNAL, list, NULL, NULL);
439         jresp = get_vcard(handle);
440
441         if (!jresp) {
442                 afb_req_fail(request, "invalid handle", NULL);
443                 return;
444         }
445
446         afb_req_success(request, jresp, "list entry");
447 }
448
449 void history(afb_req_t request)
450 {
451         struct json_object *jresp;
452         gchar *list = NULL;
453         int max_entries = -1;
454
455         if (!connected) {
456                 afb_req_fail(request, "not connected", NULL);
457                 return;
458         }
459
460         if (!parse_list_parameter(request, &list))
461                 return;
462
463         if (!parse_max_entries_parameter(request, &max_entries))
464                 return;
465
466         org_bluez_obex_phonebook_access1_call_select_sync(
467                         phonebook, INTERNAL, list, NULL, NULL);
468         jresp = get_vcards(max_entries);
469
470         afb_req_success(request, jresp, "call history");
471 }
472
473 static void search(afb_req_t request)
474 {
475         struct json_object *query, *val, *results_array, *response;
476         const char *number = NULL;
477         GVariantIter iter;
478         GVariantBuilder *b;
479         GVariant *entry, *filter, *results;
480         gchar *card, *name;
481         int max_entries = -1;
482
483         if (!connected) {
484                 afb_req_fail(request, "not connected", NULL);
485                 return;
486         }
487
488         query = afb_req_json(request);
489
490         json_object_object_get_ex(query, "number", &val);
491         if (json_object_is_type(val, json_type_string)) {
492                 number = json_object_get_string(val);
493         }
494         else {
495                 afb_req_fail(request, "no number", NULL);
496                 return;
497         }
498
499         if (!parse_max_entries_parameter(request, &max_entries))
500                 return;
501
502         org_bluez_obex_phonebook_access1_call_select_sync(
503                         phonebook, INTERNAL, CONTACTS, NULL, NULL);
504
505         b = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
506         g_variant_builder_add(b, "{sv}", "Order", g_variant_new_string("indexed"));
507         g_variant_builder_add(b, "{sv}", "Offset", g_variant_new_uint16(0));
508         g_variant_builder_add(b, "{sv}", "Format", g_variant_new_string("vcard30"));
509         if (max_entries >= 0)
510                 g_variant_builder_add(b, "{sv}", "MaxCount", g_variant_new_uint16((guint16)max_entries));
511         filter = g_variant_builder_end(b);
512
513         org_bluez_obex_phonebook_access1_call_search_sync(
514                         phonebook, "number", number, filter, &results, NULL, NULL);
515
516         g_variant_builder_unref(b);
517
518         results_array = json_object_new_array();
519         g_variant_iter_init(&iter, results);
520         while ((entry = g_variant_iter_next_value(&iter))) {
521                 g_variant_get(entry, "(ss)", &card, &name);
522                 g_variant_unref(entry);
523                 json_object *result_obj = json_object_new_object();
524                 json_object *card_str = json_object_new_string(card);
525                 json_object *name_str = json_object_new_string(name);
526                 json_object_object_add(result_obj, "handle", card_str);
527                 json_object_object_add(result_obj, "name", name_str);
528                 json_object_array_add(results_array, result_obj);
529                 g_free(card);
530                 g_free(name);
531         }
532         response = json_object_new_object();
533         json_object_object_add(response, "results", results_array);
534
535         afb_req_success(request, response, NULL);
536 }
537
538 static void status(afb_req_t request)
539 {
540         struct json_object *response, *status;
541
542         response = json_object_new_object();
543         g_mutex_lock(&connected_mutex);
544         status = json_object_new_boolean(connected);
545         g_mutex_unlock(&connected_mutex);
546         json_object_object_add(response, "connected", status);
547
548         afb_req_success(request, response, NULL);
549 }
550
551 static void subscribe(afb_req_t request)
552 {
553         const char *value = afb_req_value(request, "value");
554
555         if (!value) {
556                 afb_req_fail(request, "failed", "No event");
557                 return;
558         }
559
560         if (!g_strcmp0(value, "status")) {
561                 afb_req_subscribe(request, status_event);
562                 afb_req_success(request, NULL, NULL);
563
564                 struct json_object *event, *status;
565                 event = json_object_new_object();
566                 g_mutex_lock(&connected_mutex);
567                 status = json_object_new_boolean(connected);
568                 g_mutex_unlock(&connected_mutex);
569                 json_object_object_add(event, "connected", status);
570                 afb_event_push(status_event, event);
571         } else {
572                 afb_req_fail(request, "failed", "Invalid event");
573         }
574 }
575
576 static void unsubscribe(afb_req_t request)
577 {
578         const char *value = afb_req_value(request, "value");
579         if (value) {
580                 if (!g_strcmp0(value, "status")) {
581                         afb_req_unsubscribe(request, status_event);
582                 } else {
583                         afb_req_fail(request, "failed", "Invalid event");
584                         return;
585                 }
586         }
587
588         afb_req_success(request, NULL, NULL);
589 }
590
591 static gboolean init_session(const gchar *address)
592 {
593         GVariant *args;
594         GVariantBuilder *b;
595         const gchar *target;
596         gchar *spath;
597
598         xfer_queue = g_hash_table_new(g_str_hash, g_str_equal);
599         xfer_complete = g_hash_table_new(g_str_hash, g_str_equal);
600
601         obj_manager = object_manager_client_new_for_bus_sync(
602                         G_BUS_TYPE_SESSION,
603                         G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
604                         "org.bluez.obex", "/", NULL, NULL);
605
606         if (obj_manager == NULL) {
607                 AFB_ERROR("Failed to create object manager");
608                 return FALSE;
609         }
610
611         g_signal_connect(obj_manager,
612                         "interface-proxy-properties-changed",
613                         G_CALLBACK (on_interface_proxy_properties_changed),
614                         NULL);
615
616         client = org_bluez_obex_client1_proxy_new_for_bus_sync(
617                         G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE,
618                         "org.bluez.obex", "/org/bluez/obex", NULL, NULL);
619
620         if (client == NULL) {
621                 AFB_ERROR("Failed to create client proxy");
622                 return FALSE;
623         }
624
625         b = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
626         g_variant_builder_add(b, "{sv}", "Target", g_variant_new_string("pbap"));
627         args = g_variant_builder_end(b);
628
629         org_bluez_obex_client1_call_create_session_sync(
630                         client, address, args, &spath, NULL, NULL);
631
632         session = org_bluez_obex_session1_proxy_new_for_bus_sync(
633                         G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE,
634                         "org.bluez.obex", spath, NULL, NULL);
635
636         target = org_bluez_obex_session1_get_target(session);
637         if (g_strcmp0(target, PBAP_UUID) != 0) {
638                 AFB_ERROR("Device does not support PBAP");
639                 return FALSE;
640         }
641
642         phonebook = org_bluez_obex_phonebook_access1_proxy_new_for_bus_sync(
643                         G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE,
644                         "org.bluez.obex", spath, NULL, NULL);
645
646         return TRUE;
647 }
648
649 static gboolean is_pbap_dev_and_init(struct json_object *dev)
650 {
651         struct json_object *jresp, *props = NULL, *val = NULL;
652         int i;
653
654         json_object_object_get_ex(dev, "properties", &props);
655         if (!props)
656                 return FALSE;
657
658         json_object_object_get_ex(props, "connected", &val);
659         if (!val || !json_object_get_boolean(val))
660                 return FALSE;
661
662         json_object_object_get_ex(props, "uuids", &val);
663         for (i = 0; i < json_object_array_length(val); i++) {
664                 const char *uuid = json_object_get_string(json_object_array_get_idx(val, i));
665                 const char *address = NULL;
666                 struct json_object *val1 = NULL;
667
668                 if (g_strcmp0(PBAP_UUID, uuid))
669                         continue;
670
671                 json_object_object_get_ex(props, "address", &val1);
672                 address = json_object_get_string(val1);
673
674                 if (!address)
675                         return FALSE;
676
677                 if (init_session(address)) {
678                         jresp = json_object_new_object();
679
680                         g_mutex_lock(&connected_mutex);
681                         if (connected_address)
682                                 g_free(connected_address);
683
684                         connected = TRUE;
685                         json_object_object_add(jresp, "connected",
686                                 json_object_new_boolean(connected));
687
688                         json_object_object_get_ex(dev, "device", &val1);
689                         connected_address = g_strdup(json_object_get_string(val1));
690
691                         json_object_object_add(jresp, "device",
692                                 json_object_new_string(connected_address));
693
694                         g_mutex_unlock(&connected_mutex);
695
696                         afb_event_push(status_event, jresp);
697
698                         AFB_NOTICE("PBAP device connected: %s", connected_address);
699
700                         return TRUE;
701                 }
702                 break;
703         }
704
705         return FALSE;
706 }
707
708 static void discovery_result_cb(void *closure, struct json_object *result,
709                                 const char *error, const char *info,
710                                 afb_api_t api)
711 {
712         enum json_type type;
713         struct json_object *dev, *tmp;
714         int i;
715
716         if (!json_object_object_get_ex(result, "devices", &tmp))
717                 return;
718         type = json_object_get_type(tmp);
719
720         if (type != json_type_array)
721                 return;
722
723         for (i = 0; i < json_object_array_length(tmp); i++) {
724                 dev = json_object_array_get_idx(tmp, i);
725                 if (is_pbap_dev_and_init(dev))
726                         return;
727         }
728 }
729
730 static void init_bt(afb_api_t api)
731 {
732         struct json_object *args;
733
734         args = json_object_new_object();
735         json_object_object_add(args , "value", json_object_new_string("device_changes"));
736         afb_api_call_sync(api, "Bluetooth-Manager", "subscribe", args, NULL, NULL, NULL);
737
738         args = json_object_new_object();
739         afb_api_call(api, "Bluetooth-Manager", "managed_objects", args, discovery_result_cb, NULL);
740 }
741
742 static const afb_verb_t binding_verbs[] = {
743         { .verb = "contacts",   .callback = contacts,           .info = "List contacts" },
744         { .verb = "import",     .callback = import,             .info = "Import contacts" },
745         { .verb = "entry",      .callback = entry,              .info = "List call entry" },
746         { .verb = "history",    .callback = history,            .info = "List call history" },
747         { .verb = "search",     .callback = search,             .info = "Search for entry" },
748         { .verb = "status",     .callback = status,             .info = "Get status" },
749         { .verb = "subscribe",  .callback = subscribe,          .info = "Subscribe to events" },
750         { .verb = "unsubscribe",.callback = unsubscribe,        .info = "Unsubscribe to events" },
751         { }
752 };
753
754 static void *main_loop_thread(void *unused)
755 {
756         GMainLoop *loop = g_main_loop_new(NULL, FALSE);
757         g_main_loop_run(loop);
758
759         return NULL;
760 }
761
762 static int init(afb_api_t api)
763 {
764         AFB_NOTICE("PBAP binding init");
765
766         pthread_t tid;
767         int ret = 0;
768
769         status_event = afb_daemon_make_event("status");
770
771         ret = afb_daemon_require_api("Bluetooth-Manager", 1);
772         if (ret) {
773                 AFB_ERROR("unable to initialize bluetooth binding");
774                 return -1;
775         }
776
777         /* Start the main loop thread */
778         pthread_create(&tid, NULL, main_loop_thread, NULL);
779
780         init_bt(api);
781
782         return ret;
783 }
784
785
786 static void process_connection_event(afb_api_t api, struct json_object *object)
787 {
788         struct json_object *jresp, *val = NULL, *props = NULL;
789         const char *action, *device;
790
791         json_object_object_get_ex(object, "action", &val);
792         if (!val)
793                 return;
794         action = json_object_get_string(val);
795         if (g_strcmp0("changed", action))
796                 return;
797
798         json_object_object_get_ex(object, "properties", &props);
799         if (!props)
800                 return;
801
802         json_object_object_get_ex(props, "connected", &val);
803         if (!val)
804                 return;
805
806         if (json_object_get_boolean(val)) {
807                 struct json_object *args = json_object_new_object();
808                 afb_api_call(api, "Bluetooth-Manager", "managed_objects",
809                              args, discovery_result_cb, NULL);
810                 return;
811         }
812
813         json_object_object_get_ex(object, "device", &val);
814         if (!val)
815                 return;
816
817         device = json_object_get_string(val);
818         jresp = json_object_new_object();
819
820         g_mutex_lock(&connected_mutex);
821         connected = FALSE;
822         json_object_object_add(jresp, "connected", json_object_new_boolean(connected));
823         json_object_object_add(jresp, "device", json_object_new_string(device));
824         g_mutex_unlock(&connected_mutex);
825
826         afb_event_push(status_event, jresp);
827
828         AFB_NOTICE("PBAP device disconnected: %s", device);
829 }
830
831 static void onevent(afb_api_t api, const char *event, struct json_object *object)
832 {
833         if (!g_ascii_strcasecmp(event, "Bluetooth-Manager/device_changes"))
834                 process_connection_event(api, object);
835         else
836                 AFB_ERROR("Unsupported event: %s\n", event);
837 }
838
839 const afb_binding_t afbBindingV3 = {
840         .api = "bluetooth-pbap",
841         .verbs = binding_verbs,
842         .init = init,
843         .onevent = onevent,
844 };