1/*
2 *
3 *  BlueZ - Bluetooth protocol stack for Linux
4 *
5 *  Copyright (C) 2006-2010  Nokia Corporation
6 *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
7 *
8 *
9 *  This program is free software; you can redistribute it and/or modify
10 *  it under the terms of the GNU General Public License as published by
11 *  the Free Software Foundation; either version 2 of the License, or
12 *  (at your option) any later version.
13 *
14 *  This program is distributed in the hope that it will be useful,
15 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 *  GNU General Public License for more details.
18 *
19 *  You should have received a copy of the GNU General Public License
20 *  along with this program; if not, write to the Free Software
21 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 */
24
25#ifdef HAVE_CONFIG_H
26#include <config.h>
27#endif
28
29#include <stdlib.h>
30#include <stdio.h>
31#include <stdint.h>
32#include <glib.h>
33#include <dbus/dbus.h>
34#include <gdbus.h>
35
36#include "log.h"
37#include "telephony.h"
38
39#define TELEPHONY_DUMMY_IFACE "org.bluez.TelephonyTest"
40#define TELEPHONY_DUMMY_PATH "/org/bluez/test"
41
42static DBusConnection *connection = NULL;
43
44static const char *chld_str = "0,1,1x,2,2x,3,4";
45static char *subscriber_number = NULL;
46static char *active_call_number = NULL;
47static int active_call_status = 0;
48static int active_call_dir = 0;
49
50static gboolean events_enabled = FALSE;
51
52/* Response and hold state
53 * -1 = none
54 *  0 = incoming call is put on hold in the AG
55 *  1 = held incoming call is accepted in the AG
56 *  2 = held incoming call is rejected in the AG
57 */
58static int response_and_hold = -1;
59
60static struct indicator dummy_indicators[] =
61{
62	{ "battchg",	"0-5",	5,	TRUE },
63	{ "signal",	"0-5",	5,	TRUE },
64	{ "service",	"0,1",	1,	TRUE },
65	{ "call",	"0,1",	0,	TRUE },
66	{ "callsetup",	"0-3",	0,	TRUE },
67	{ "callheld",	"0-2",	0,	FALSE },
68	{ "roam",	"0,1",	0,	TRUE },
69	{ NULL }
70};
71
72static inline DBusMessage *invalid_args(DBusMessage *msg)
73{
74	return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments",
75					"Invalid arguments in method call");
76}
77
78void telephony_device_connected(void *telephony_device)
79{
80	DBG("telephony-dummy: device %p connected", telephony_device);
81}
82
83void telephony_device_disconnected(void *telephony_device)
84{
85	DBG("telephony-dummy: device %p disconnected", telephony_device);
86	events_enabled = FALSE;
87}
88
89void telephony_event_reporting_req(void *telephony_device, int ind)
90{
91	events_enabled = ind == 1 ? TRUE : FALSE;
92
93	telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE);
94}
95
96void telephony_response_and_hold_req(void *telephony_device, int rh)
97{
98	response_and_hold = rh;
99
100	telephony_response_and_hold_ind(response_and_hold);
101
102	telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE);
103}
104
105void telephony_last_dialed_number_req(void *telephony_device)
106{
107	telephony_last_dialed_number_rsp(telephony_device, CME_ERROR_NONE);
108
109	/* Notify outgoing call set-up successfully initiated */
110	telephony_update_indicator(dummy_indicators, "callsetup",
111					EV_CALLSETUP_OUTGOING);
112	telephony_update_indicator(dummy_indicators, "callsetup",
113					EV_CALLSETUP_ALERTING);
114
115	active_call_status = CALL_STATUS_ALERTING;
116	active_call_dir = CALL_DIR_OUTGOING;
117}
118
119void telephony_terminate_call_req(void *telephony_device)
120{
121	g_free(active_call_number);
122	active_call_number = NULL;
123
124	telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE);
125
126	if (telephony_get_indicator(dummy_indicators, "callsetup") > 0)
127		telephony_update_indicator(dummy_indicators, "callsetup",
128						EV_CALLSETUP_INACTIVE);
129	else
130		telephony_update_indicator(dummy_indicators, "call",
131						EV_CALL_INACTIVE);
132}
133
134void telephony_answer_call_req(void *telephony_device)
135{
136	telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
137
138	telephony_update_indicator(dummy_indicators, "call", EV_CALL_ACTIVE);
139	telephony_update_indicator(dummy_indicators, "callsetup",
140					EV_CALLSETUP_INACTIVE);
141
142	active_call_status = CALL_STATUS_ACTIVE;
143}
144
145void telephony_dial_number_req(void *telephony_device, const char *number)
146{
147	g_free(active_call_number);
148	active_call_number = g_strdup(number);
149
150	DBG("telephony-dummy: dial request to %s", active_call_number);
151
152	telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);
153
154	/* Notify outgoing call set-up successfully initiated */
155	telephony_update_indicator(dummy_indicators, "callsetup",
156					EV_CALLSETUP_OUTGOING);
157	telephony_update_indicator(dummy_indicators, "callsetup",
158					EV_CALLSETUP_ALERTING);
159
160	active_call_status = CALL_STATUS_ALERTING;
161	active_call_dir = CALL_DIR_OUTGOING;
162}
163
164void telephony_transmit_dtmf_req(void *telephony_device, char tone)
165{
166	DBG("telephony-dummy: transmit dtmf: %c", tone);
167	telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE);
168}
169
170void telephony_subscriber_number_req(void *telephony_device)
171{
172	DBG("telephony-dummy: subscriber number request");
173	if (subscriber_number)
174		telephony_subscriber_number_ind(subscriber_number,
175						NUMBER_TYPE_TELEPHONY,
176						SUBSCRIBER_SERVICE_VOICE);
177	telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE);
178}
179
180void telephony_list_current_calls_req(void *telephony_device)
181{
182	DBG("telephony-dummy: list current calls request");
183	if (active_call_number)
184		telephony_list_current_call_ind(1, active_call_dir,
185						active_call_status,
186						CALL_MODE_VOICE,
187						CALL_MULTIPARTY_NO,
188						active_call_number,
189						NUMBER_TYPE_TELEPHONY);
190	telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
191}
192
193void telephony_operator_selection_req(void *telephony_device)
194{
195	telephony_operator_selection_ind(OPERATOR_MODE_AUTO, "DummyOperator");
196	telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE);
197}
198
199void telephony_call_hold_req(void *telephony_device, const char *cmd)
200{
201	DBG("telephony-dymmy: got call hold request %s", cmd);
202	telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE);
203}
204
205void telephony_nr_and_ec_req(void *telephony_device, gboolean enable)
206{
207	DBG("telephony-dummy: got %s NR and EC request",
208			enable ? "enable" : "disable");
209
210	telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE);
211}
212
213void telephony_voice_dial_req(void *telephony_device, gboolean enable)
214{
215	DBG("telephony-dummy: got %s voice dial request",
216			enable ? "enable" : "disable");
217
218	g_dbus_emit_signal(connection, TELEPHONY_DUMMY_PATH,
219			TELEPHONY_DUMMY_IFACE, "VoiceDial",
220			DBUS_TYPE_INVALID);
221
222	telephony_voice_dial_rsp(telephony_device, CME_ERROR_NONE);
223}
224
225void telephony_key_press_req(void *telephony_device, const char *keys)
226{
227	DBG("telephony-dummy: got key press request for %s", keys);
228	telephony_key_press_rsp(telephony_device, CME_ERROR_NONE);
229}
230
231/* D-Bus method handlers */
232static DBusMessage *outgoing_call(DBusConnection *conn, DBusMessage *msg,
233					void *data)
234{
235	const char *number;
236
237	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
238						DBUS_TYPE_INVALID))
239		return invalid_args(msg);
240
241	DBG("telephony-dummy: outgoing call to %s", number);
242
243	g_free(active_call_number);
244	active_call_number = g_strdup(number);
245
246	telephony_update_indicator(dummy_indicators, "callsetup",
247					EV_CALLSETUP_OUTGOING);
248	telephony_update_indicator(dummy_indicators, "callsetup",
249					EV_CALLSETUP_ALERTING);
250
251	active_call_status = CALL_STATUS_ALERTING;
252	active_call_dir = CALL_DIR_OUTGOING;
253
254	return dbus_message_new_method_return(msg);
255}
256
257static DBusMessage *incoming_call(DBusConnection *conn, DBusMessage *msg,
258					void *data)
259{
260	const char *number;
261
262	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
263						DBUS_TYPE_INVALID))
264		return invalid_args(msg);
265
266	DBG("telephony-dummy: incoming call to %s", number);
267
268	g_free(active_call_number);
269	active_call_number = g_strdup(number);
270
271	telephony_update_indicator(dummy_indicators, "callsetup",
272					EV_CALLSETUP_INCOMING);
273
274	active_call_status = CALL_STATUS_INCOMING;
275	active_call_dir = CALL_DIR_INCOMING;
276
277	telephony_incoming_call_ind(number, NUMBER_TYPE_TELEPHONY);
278
279	return dbus_message_new_method_return(msg);
280}
281
282static DBusMessage *cancel_call(DBusConnection *conn, DBusMessage *msg,
283					void *data)
284{
285	DBG("telephony-dummy: cancel call");
286
287	g_free(active_call_number);
288	active_call_number = NULL;
289
290	if (telephony_get_indicator(dummy_indicators, "callsetup") > 0) {
291		telephony_update_indicator(dummy_indicators, "callsetup",
292						EV_CALLSETUP_INACTIVE);
293		telephony_calling_stopped_ind();
294	}
295
296	if (telephony_get_indicator(dummy_indicators, "call") > 0)
297		telephony_update_indicator(dummy_indicators, "call",
298						EV_CALL_INACTIVE);
299
300	return dbus_message_new_method_return(msg);
301}
302
303static DBusMessage *signal_strength(DBusConnection *conn, DBusMessage *msg,
304					void *data)
305{
306	dbus_uint32_t strength;
307
308	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &strength,
309						DBUS_TYPE_INVALID))
310		return invalid_args(msg);
311
312	if (strength > 5)
313		return invalid_args(msg);
314
315	telephony_update_indicator(dummy_indicators, "signal", strength);
316
317	DBG("telephony-dummy: signal strength set to %u", strength);
318
319	return dbus_message_new_method_return(msg);
320}
321
322static DBusMessage *battery_level(DBusConnection *conn, DBusMessage *msg,
323					void *data)
324{
325	dbus_uint32_t level;
326
327	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &level,
328						DBUS_TYPE_INVALID))
329		return invalid_args(msg);
330
331	if (level > 5)
332		return invalid_args(msg);
333
334	telephony_update_indicator(dummy_indicators, "battchg", level);
335
336	DBG("telephony-dummy: battery level set to %u", level);
337
338	return dbus_message_new_method_return(msg);
339}
340
341static DBusMessage *roaming_status(DBusConnection *conn, DBusMessage *msg,
342					void *data)
343{
344	dbus_bool_t roaming;
345	int val;
346
347	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &roaming,
348						DBUS_TYPE_INVALID))
349		return invalid_args(msg);
350
351	val = roaming ? EV_ROAM_ACTIVE : EV_ROAM_INACTIVE;
352
353	telephony_update_indicator(dummy_indicators, "roam", val);
354
355	DBG("telephony-dummy: roaming status set to %d", val);
356
357	return dbus_message_new_method_return(msg);
358}
359
360static DBusMessage *registration_status(DBusConnection *conn, DBusMessage *msg,
361					void *data)
362{
363	dbus_bool_t registration;
364	int val;
365
366	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &registration,
367						DBUS_TYPE_INVALID))
368		return invalid_args(msg);
369
370	val = registration ? EV_SERVICE_PRESENT : EV_SERVICE_NONE;
371
372	telephony_update_indicator(dummy_indicators, "service", val);
373
374	DBG("telephony-dummy: registration status set to %d", val);
375
376	return dbus_message_new_method_return(msg);
377}
378
379static DBusMessage *set_subscriber_number(DBusConnection *conn,
380						DBusMessage *msg,
381						void *data)
382{
383	const char *number;
384
385	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
386						DBUS_TYPE_INVALID))
387		return invalid_args(msg);
388
389	g_free(subscriber_number);
390	subscriber_number = g_strdup(number);
391
392	DBG("telephony-dummy: subscriber number set to %s", number);
393
394	return dbus_message_new_method_return(msg);
395}
396
397static GDBusMethodTable dummy_methods[] = {
398	{ "OutgoingCall",	"s",	"",	outgoing_call		},
399	{ "IncomingCall",	"s",	"",	incoming_call		},
400	{ "CancelCall",		"",	"",	cancel_call		},
401	{ "SignalStrength",	"u",	"",	signal_strength		},
402	{ "BatteryLevel",	"u",	"",	battery_level		},
403	{ "RoamingStatus",	"b",	"",	roaming_status		},
404	{ "RegistrationStatus",	"b",	"",	registration_status	},
405	{ "SetSubscriberNumber","s",	"",	set_subscriber_number	},
406	{ }
407};
408
409static GDBusSignalTable dummy_signals[] = {
410	{ "VoiceDial",	"" },
411	{ }
412};
413
414int telephony_init(void)
415{
416	uint32_t features = AG_FEATURE_REJECT_A_CALL |
417				AG_FEATURE_ENHANCED_CALL_STATUS |
418				AG_FEATURE_EXTENDED_ERROR_RESULT_CODES;
419
420	connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
421
422	if (g_dbus_register_interface(connection, TELEPHONY_DUMMY_PATH,
423					TELEPHONY_DUMMY_IFACE,
424					dummy_methods, dummy_signals,
425					NULL, NULL, NULL) == FALSE) {
426		error("telephony-dummy interface %s init failed on path %s",
427			TELEPHONY_DUMMY_IFACE, TELEPHONY_DUMMY_PATH);
428		return -1;
429	}
430
431	telephony_ready_ind(features, dummy_indicators, response_and_hold,
432				chld_str);
433
434	return 0;
435}
436
437void telephony_exit(void)
438{
439	dbus_connection_unref(connection);
440	connection = NULL;
441}
442