hfp_ag_bluez5: Try to support non-phone hardware
[platform/upstream/ofono.git] / plugins / hfp_ag_bluez5.c
1 /*
2  *  oFono - Open Source Telephony
3  *
4  *  Copyright (C) 2011  Intel Corporation. All rights reserved.
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License version 2 as
8  *  published by the Free Software Foundation.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 #include <stdio.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <errno.h>
28 #include <stdint.h>
29 #include <sys/socket.h>
30 #include <glib.h>
31 #include <ofono.h>
32 #include <gdbus.h>
33
34 #define OFONO_API_SUBJECT_TO_CHANGE
35 #include <ofono/plugin.h>
36 #include <ofono/log.h>
37 #include <ofono/modem.h>
38
39 #include "hfp.h"
40 #include "bluez5.h"
41
42 #ifndef DBUS_TYPE_UNIX_FD
43 #define DBUS_TYPE_UNIX_FD -1
44 #endif
45
46 #define HFP_AG_EXT_PROFILE_PATH   "/bluetooth/profile/hfp_ag"
47
48 static guint modemwatch_id;
49 static GList *modems;
50 static GHashTable *sim_hash = NULL;
51 static GHashTable *connection_hash;
52
53 static void connection_destroy(gpointer data)
54 {
55         int fd = GPOINTER_TO_INT(data);
56
57         DBG("fd %d", fd);
58
59         close(fd);
60 }
61
62 static gboolean io_hup_cb(GIOChannel *io, GIOCondition cond, gpointer data)
63 {
64         char *device = data;
65
66         DBG("Remove %s", device);
67
68         g_hash_table_remove(connection_hash, device);
69
70         return FALSE;
71 }
72
73 static DBusMessage *profile_new_connection(DBusConnection *conn,
74                                                 DBusMessage *msg, void *data)
75 {
76         DBusMessageIter entry;
77         const char *device;
78         GIOChannel *io;
79         int fd, fd_dup;
80         struct ofono_emulator *em;
81         struct ofono_modem *modem;
82
83         DBG("Profile handler NewConnection");
84
85         if (dbus_message_iter_init(msg, &entry) == FALSE)
86                 goto invalid;
87
88         if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_OBJECT_PATH)
89                 goto invalid;
90
91         dbus_message_iter_get_basic(&entry, &device);
92         dbus_message_iter_next(&entry);
93
94         if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_UNIX_FD)
95                 goto invalid;
96
97         dbus_message_iter_get_basic(&entry, &fd);
98         dbus_message_iter_next(&entry);
99
100         if (fd < 0)
101                 goto invalid;
102
103         DBG("%s", device);
104
105         /* Pick the first voicecall capable modem */
106         if (modems == NULL) {
107                 close(fd);
108                 return g_dbus_create_error(msg, BLUEZ_ERROR_INTERFACE
109                                                 ".Rejected",
110                                                 "No voice call capable modem");
111         }
112
113         modem = modems->data;
114         DBG("Picked modem %p for emulator", modem);
115
116         em = ofono_emulator_create(modem, OFONO_EMULATOR_TYPE_HFP);
117         if (em == NULL) {
118                 close(fd);
119                 return g_dbus_create_error(msg, BLUEZ_ERROR_INTERFACE
120                                                 ".Rejected",
121                                                 "Not enough resources");
122         }
123
124         ofono_emulator_register(em, fd);
125
126         fd_dup = dup(fd);
127         io = g_io_channel_unix_new(fd_dup);
128         g_io_add_watch_full(io, G_PRIORITY_DEFAULT, G_IO_HUP, io_hup_cb,
129                                                 g_strdup(device), g_free);
130         g_io_channel_unref(io);
131
132         g_hash_table_insert(connection_hash, g_strdup(device),
133                                                 GINT_TO_POINTER(fd_dup));
134
135         return dbus_message_new_method_return(msg);
136
137 invalid:
138         return g_dbus_create_error(msg, BLUEZ_ERROR_INTERFACE ".Rejected",
139                                         "Invalid arguments in method call");
140 }
141
142 static DBusMessage *profile_release(DBusConnection *conn,
143                                         DBusMessage *msg, void *user_data)
144 {
145         DBG("Profile handler Release");
146
147         return g_dbus_create_error(msg, BLUEZ_ERROR_INTERFACE
148                                                 ".NotImplemented",
149                                                 "Implementation not provided");
150 }
151
152 static DBusMessage *profile_cancel(DBusConnection *conn,
153                                         DBusMessage *msg, void *user_data)
154 {
155         DBG("Profile handler Cancel");
156
157         return g_dbus_create_error(msg, BLUEZ_ERROR_INTERFACE
158                                         ".NotImplemented",
159                                         "Implementation not provided");
160 }
161
162 static DBusMessage *profile_disconnection(DBusConnection *conn,
163                                         DBusMessage *msg, void *user_data)
164 {
165         DBusMessageIter iter;
166         const char *device;
167         gpointer fd;
168
169         DBG("Profile handler RequestDisconnection");
170
171         if (!dbus_message_iter_init(msg, &iter))
172                 goto invalid;
173
174         if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH)
175                 goto invalid;
176
177         dbus_message_iter_get_basic(&iter, &device);
178
179         DBG("%s", device);
180
181         fd = g_hash_table_lookup(connection_hash, device);
182         if (fd == NULL)
183                 goto invalid;
184
185         shutdown(GPOINTER_TO_INT(fd), SHUT_RDWR);
186
187         g_hash_table_remove(connection_hash, device);
188
189         return dbus_message_new_method_return(msg);
190
191 invalid:
192         return g_dbus_create_error(msg, BLUEZ_ERROR_INTERFACE ".Rejected",
193                                         "Invalid arguments in method call");
194 }
195
196 static const GDBusMethodTable profile_methods[] = {
197         { GDBUS_ASYNC_METHOD("NewConnection",
198                                 GDBUS_ARGS({ "device", "o"}, { "fd", "h"},
199                                                 { "fd_properties", "a{sv}" }),
200                                 NULL, profile_new_connection) },
201         { GDBUS_METHOD("Release", NULL, NULL, profile_release) },
202         { GDBUS_METHOD("Cancel", NULL, NULL, profile_cancel) },
203         { GDBUS_METHOD("RequestDisconnection",
204                                 GDBUS_ARGS({"device", "o"}), NULL,
205                                 profile_disconnection) },
206         { }
207 };
208
209 static void sim_state_watch(enum ofono_sim_state new_state, void *data)
210 {
211         struct ofono_modem *modem = data;
212         DBusConnection *conn = ofono_dbus_get_connection();
213
214         if (new_state != OFONO_SIM_STATE_READY) {
215                 if (modems == NULL)
216                         return;
217
218                 modems = g_list_remove(modems, modem);
219                 if (modems != NULL)
220                         return;
221
222                 bt_unregister_profile(conn, HFP_AG_EXT_PROFILE_PATH);
223
224                 return;
225         }
226
227         if (__ofono_modem_find_atom(modem, OFONO_ATOM_TYPE_VOICECALL) == NULL)
228                 return;
229
230         modems = g_list_append(modems, modem);
231
232         if (modems->next != NULL)
233                 return;
234
235         bt_register_profile(conn, HFP_AG_UUID, HFP_VERSION_1_5, "hfp_ag",
236                                         HFP_AG_EXT_PROFILE_PATH, NULL, 0);
237 }
238
239 static gboolean sim_watch_remove(gpointer key, gpointer value,
240                                 gpointer user_data)
241 {
242         struct ofono_sim *sim = key;
243
244         ofono_sim_remove_state_watch(sim, GPOINTER_TO_UINT(value));
245
246         return TRUE;
247 }
248
249 static void sim_watch(struct ofono_atom *atom,
250                                 enum ofono_atom_watch_condition cond,
251                                 void *data)
252 {
253         struct ofono_sim *sim = __ofono_atom_get_data(atom);
254         struct ofono_modem *modem = data;
255         int watch;
256
257         if (cond == OFONO_ATOM_WATCH_CONDITION_UNREGISTERED) {
258                 sim_state_watch(OFONO_SIM_STATE_NOT_PRESENT, modem);
259
260                 sim_watch_remove(sim, g_hash_table_lookup(sim_hash, sim), NULL);
261                 g_hash_table_remove(sim_hash, sim);
262
263                 return;
264         }
265
266         watch = ofono_sim_add_state_watch(sim, sim_state_watch, modem, NULL);
267         g_hash_table_insert(sim_hash, sim, GUINT_TO_POINTER(watch));
268         sim_state_watch(ofono_sim_get_state(sim), modem);
269 }
270
271 static void voicecall_watch(struct ofono_atom *atom,
272                                 enum ofono_atom_watch_condition cond,
273                                 void *data)
274 {
275         struct ofono_atom *sim_atom;
276         struct ofono_sim *sim;
277         struct ofono_modem *modem;
278         DBusConnection *conn = ofono_dbus_get_connection();
279
280         if (cond == OFONO_ATOM_WATCH_CONDITION_UNREGISTERED)
281                 return;
282
283         /*
284          * This logic is only intended to handle voicecall atoms
285          * registered in post_sim state or later
286          */
287         modem = __ofono_atom_get_modem(atom);
288
289         sim_atom = __ofono_modem_find_atom(modem, OFONO_ATOM_TYPE_SIM);
290         if (sim_atom == NULL)
291                 return;
292
293         sim = __ofono_atom_get_data(sim_atom);
294         if (ofono_sim_get_state(sim) != OFONO_SIM_STATE_READY)
295                 return;
296
297         modems = g_list_append(modems, modem);
298
299         if (modems->next != NULL)
300                 return;
301
302         bt_register_profile(conn, HFP_AG_UUID, HFP_VERSION_1_5, "hfp_ag",
303                                         HFP_AG_EXT_PROFILE_PATH, NULL, 0);
304 }
305
306 static void modem_watch(struct ofono_modem *modem, gboolean added, void *user)
307 {
308         DBG("modem: %p, added: %d", modem, added);
309
310         if (added == FALSE)
311                 return;
312
313         __ofono_modem_add_atom_watch(modem, OFONO_ATOM_TYPE_SIM,
314                                         sim_watch, modem, NULL);
315         __ofono_modem_add_atom_watch(modem, OFONO_ATOM_TYPE_VOICECALL,
316                                         voicecall_watch, modem, NULL);
317 }
318
319 static void call_modemwatch(struct ofono_modem *modem, void *user)
320 {
321         modem_watch(modem, TRUE, user);
322 }
323
324 static int hfp_ag_init(void)
325 {
326         DBusConnection *conn = ofono_dbus_get_connection();
327
328         if (DBUS_TYPE_UNIX_FD < 0)
329                 return -EBADF;
330
331         /* Registers External Profile handler */
332         if (!g_dbus_register_interface(conn, HFP_AG_EXT_PROFILE_PATH,
333                                         BLUEZ_PROFILE_INTERFACE,
334                                         profile_methods, NULL,
335                                         NULL, NULL, NULL)) {
336                 ofono_error("Register Profile interface failed: %s",
337                                                 HFP_AG_EXT_PROFILE_PATH);
338                 return -EIO;
339         }
340
341         sim_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
342
343         modemwatch_id = __ofono_modemwatch_add(modem_watch, NULL, NULL);
344         __ofono_modem_foreach(call_modemwatch, NULL);
345
346         connection_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
347                                         g_free, connection_destroy);
348
349         return 0;
350 }
351
352 static void hfp_ag_exit(void)
353 {
354         DBusConnection *conn = ofono_dbus_get_connection();
355
356         __ofono_modemwatch_remove(modemwatch_id);
357         g_dbus_unregister_interface(conn, HFP_AG_EXT_PROFILE_PATH,
358                                                 BLUEZ_PROFILE_INTERFACE);
359
360         g_hash_table_destroy(connection_hash);
361
362         g_list_free(modems);
363         g_hash_table_foreach_remove(sim_hash, sim_watch_remove, NULL);
364         g_hash_table_destroy(sim_hash);
365 }
366
367 OFONO_PLUGIN_DEFINE(hfp_ag_bluez5, "Hands-Free Audio Gateway Profile Plugins",
368                                 VERSION, OFONO_PLUGIN_PRIORITY_DEFAULT,
369                                 hfp_ag_init, hfp_ag_exit)