/* * * oFono - Open Source Telephony * * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "ofono.h" #include "driver.h" #include "common.h" #include "dbus-gsm.h" #include "modem.h" #define VOICECALL_MANAGER_INTERFACE "org.ofono.VoiceCallManager" #define VOICECALL_INTERFACE "org.ofono.VoiceCall" #define VOICECALLS_FLAG_PENDING 0x1 #define VOICECALLS_FLAG_MULTI_RELEASE 0x2 #define VOICECALLS_FLAG_UPDATING_CALL_LIST 0x4 #define VOICECALLS_FLAG_UPDATING_MPTY_CALL_LIST 0x8 #define MAX_VOICE_CALLS 16 struct voicecalls_data { GSList *call_list; GSList *release_list; GSList *multiparty_list; struct ofono_voicecall_ops *ops; int flags; DBusMessage *pending; }; struct voicecall { struct ofono_call *call; struct ofono_modem *modem; time_t start_time; }; static void generic_callback(const struct ofono_error *error, void *data); static void dial_callback(const struct ofono_error *error, void *data); static void multirelease_callback(const struct ofono_error *err, void *data); static void multiparty_create_callback(const struct ofono_error *error, void *data); static void private_chat_callback(const struct ofono_error *error, void *data); static gint call_compare_by_id(gconstpointer a, gconstpointer b) { const struct ofono_call *call = ((struct voicecall *)a)->call; unsigned int id = GPOINTER_TO_UINT(b); if (id < call->id) return -1; if (id > call->id) return 1; return 0; } static gint call_compare(gconstpointer a, gconstpointer b) { const struct voicecall *ca = a; const struct voicecall *cb = b; if (ca->call->id < cb->call->id) return -1; if (ca->call->id > cb->call->id) return 1; return 0; } static const char *call_status_to_string(int status) { switch (status) { case CALL_STATUS_ACTIVE: return "active"; case CALL_STATUS_HELD: return "held"; case CALL_STATUS_DIALING: return "dialing"; case CALL_STATUS_ALERTING: return "alerting"; case CALL_STATUS_INCOMING: return "incoming"; case CALL_STATUS_WAITING: return "waiting"; default: return "disconnected"; } } static const char *phone_and_clip_to_string(const char *number, int type, int clip_validity) { if (clip_validity == CLIP_VALIDITY_WITHHELD && !strlen(number)) return "withheld"; if (clip_validity == CLIP_VALIDITY_NOT_AVAILABLE) return ""; return phone_number_to_string(number, type); } static const char *time_to_str(const time_t *t) { static char buf[128]; strftime(buf, 127, "%a, %d %b %Y %H:%M:%S %z", localtime(t)); buf[127] = '\0'; return buf; } static DBusMessage *voicecall_get_properties(DBusConnection *conn, DBusMessage *msg, void *data) { struct voicecall *v = data; struct ofono_call *call = v->call; DBusMessage *reply; DBusMessageIter iter; DBusMessageIter dict; const char *status; const char *callerid; //char timebuf[512]; const char *timestr = ""; reply = dbus_message_new_method_return(msg); if (!reply) return NULL; status = call_status_to_string(call->status); callerid = phone_number_to_string(call->phone_number, call->number_type); dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, PROPERTIES_ARRAY_SIGNATURE, &dict); dbus_gsm_dict_append(&dict, "State", DBUS_TYPE_STRING, &status); dbus_gsm_dict_append(&dict, "LineIdentification", DBUS_TYPE_STRING, &callerid); if (call->status == CALL_STATUS_ACTIVE || (call->status == CALL_STATUS_DISCONNECTED && v->start_time != 0) || call->status == CALL_STATUS_HELD) { timestr = time_to_str(&v->start_time); dbus_gsm_dict_append(&dict, "StartTime", DBUS_TYPE_STRING, ×tr); } dbus_message_iter_close_container(&iter, &dict); return reply; } static DBusMessage *voicecall_busy(DBusConnection *conn, DBusMessage *msg, void *data) { struct voicecall *v = data; struct ofono_modem *modem = v->modem; struct voicecalls_data *voicecalls = modem->voicecalls; struct ofono_call *call = v->call; if (call->status != CALL_STATUS_INCOMING && call->status != CALL_STATUS_WAITING) return dbus_gsm_failed(msg); if (!voicecalls->ops->release_specific) return dbus_gsm_not_implemented(msg); if (voicecalls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); voicecalls->flags |= VOICECALLS_FLAG_PENDING; voicecalls->pending = dbus_message_ref(msg); voicecalls->ops->set_udub(modem, generic_callback, voicecalls); return NULL; } static DBusMessage *voicecall_deflect(DBusConnection *conn, DBusMessage *msg, void *data) { struct voicecall *v = data; struct ofono_modem *modem = v->modem; struct voicecalls_data *voicecalls = modem->voicecalls; struct ofono_call *call = v->call; const char *number; int number_type; if (call->status != CALL_STATUS_INCOMING && call->status != CALL_STATUS_WAITING) return dbus_gsm_failed(msg); if (!voicecalls->ops->deflect) return dbus_gsm_not_implemented(msg); if (voicecalls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, DBUS_TYPE_INVALID) == FALSE) return dbus_gsm_invalid_args(msg); if (!valid_phone_number_format(number)) return dbus_gsm_invalid_format(msg); voicecalls->flags |= VOICECALLS_FLAG_PENDING; voicecalls->pending = dbus_message_ref(msg); string_to_phone_number(number, &number_type, &number); voicecalls->ops->deflect(modem, number, number_type, generic_callback, voicecalls); return NULL; } static DBusMessage *voicecall_hangup(DBusConnection *conn, DBusMessage *msg, void *data) { struct voicecall *v = data; struct ofono_modem *modem = v->modem; struct voicecalls_data *voicecalls = modem->voicecalls; struct ofono_call *call = v->call; if (call->status == CALL_STATUS_DISCONNECTED) return dbus_gsm_failed(msg); if (!voicecalls->ops->release_specific) return dbus_gsm_not_implemented(msg); if (voicecalls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); voicecalls->flags |= VOICECALLS_FLAG_PENDING; voicecalls->pending = dbus_message_ref(msg); voicecalls->ops->release_specific(modem, call->id, generic_callback, voicecalls); return NULL; } static DBusMessage *voicecall_answer(DBusConnection *conn, DBusMessage *msg, void *data) { struct voicecall *v = data; struct ofono_modem *modem = v->modem; struct voicecalls_data *voicecalls = modem->voicecalls; struct ofono_call *call = v->call; if (call->status != CALL_STATUS_INCOMING) return dbus_gsm_failed(msg); if (!voicecalls->ops->answer) return dbus_gsm_not_implemented(msg); if (voicecalls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); voicecalls->flags |= VOICECALLS_FLAG_PENDING; voicecalls->pending = dbus_message_ref(msg); voicecalls->ops->answer(modem, generic_callback, voicecalls); return NULL; } static GDBusMethodTable voicecall_methods[] = { { "GetProperties", "", "a{sv}", voicecall_get_properties }, { "Busy", "", "", voicecall_busy, G_DBUS_METHOD_FLAG_ASYNC }, { "Deflect", "s", "", voicecall_deflect, G_DBUS_METHOD_FLAG_ASYNC }, { "Hangup", "", "", voicecall_hangup, G_DBUS_METHOD_FLAG_ASYNC }, { "Answer", "", "", voicecall_answer, G_DBUS_METHOD_FLAG_ASYNC }, { } }; static GDBusSignalTable voicecall_signals[] = { { "PropertyChanged", "sv" }, { "DisconnectReason", "s" }, { } }; static struct voicecall *voicecall_create(struct ofono_modem *modem, struct ofono_call *call) { struct voicecall *v; v = g_try_new0(struct voicecall, 1); if (!v) return NULL; v->call = call; v->modem = modem; return v; } static void voicecall_destroy(gpointer userdata) { struct voicecall *voicecall = (struct voicecall *)userdata; g_free(voicecall->call); g_free(voicecall); } static const char *voicecall_build_path(struct ofono_modem *modem, const struct ofono_call *call) { static char path[MAX_DBUS_PATH_LEN]; snprintf(path, MAX_DBUS_PATH_LEN, "%s/voicecall%02d", modem->path, call->id); return path; } static void voicecall_set_call_status(struct ofono_modem *modem, struct voicecall *call, int status) { DBusConnection *conn = dbus_gsm_connection(); const char *path; const char *status_str; int old_status; if (call->call->status == status) return; old_status = call->call->status; call->call->status = status; status_str = call_status_to_string(status); path = voicecall_build_path(modem, call->call); dbus_gsm_signal_property_changed(conn, path, VOICECALL_INTERFACE, "State", DBUS_TYPE_STRING, &status_str); if (status == CALL_STATUS_ACTIVE && (old_status == CALL_STATUS_INCOMING || old_status == CALL_STATUS_DIALING || old_status == CALL_STATUS_ALERTING || old_status == CALL_STATUS_WAITING)) { const char *timestr; call->start_time = time(NULL); timestr = time_to_str(&call->start_time); dbus_gsm_signal_property_changed(conn, path, VOICECALL_INTERFACE, "StartTime", DBUS_TYPE_STRING, ×tr); } } static void voicecall_set_call_lineid(struct ofono_modem *modem, struct voicecall *v, const char *number, int number_type, int clip_validity) { struct ofono_call *call = v->call; DBusConnection *conn = dbus_gsm_connection(); const char *path; const char *lineid_str; if (!strcmp(call->phone_number, number) && call->number_type == number_type && call->clip_validity == clip_validity) return; /* Two cases: We get an incoming call with CLIP factored in, or * CLIP comes in later as a separate event * For COLP only the phone number should be checked, it can come * in with the initial call event or later as a separate event */ /* For plugins that don't keep state, ignore */ if (call->clip_validity == CLIP_VALIDITY_VALID && clip_validity == CLIP_VALIDITY_NOT_AVAILABLE) return; strcpy(call->phone_number, number); call->clip_validity = clip_validity; call->number_type = number_type; path = voicecall_build_path(modem, call); if (call->direction == CALL_DIRECTION_MOBILE_TERMINATED) lineid_str = phone_and_clip_to_string(number, number_type, clip_validity); else lineid_str = phone_number_to_string(number, number_type); dbus_gsm_signal_property_changed(conn, path, VOICECALL_INTERFACE, "LineIdentification", DBUS_TYPE_STRING, &lineid_str); } static gboolean voicecall_dbus_register(struct voicecall *voicecall) { DBusConnection *conn = dbus_gsm_connection(); const char *path; if (!voicecall) return FALSE; path = voicecall_build_path(voicecall->modem, voicecall->call); if (!g_dbus_register_interface(conn, path, VOICECALL_INTERFACE, voicecall_methods, voicecall_signals, NULL, voicecall, voicecall_destroy)) { ofono_error("Could not register VoiceCall %s", path); voicecall_destroy(voicecall); return FALSE; } return TRUE; } static gboolean voicecall_dbus_unregister(struct ofono_modem *modem, struct voicecall *call) { DBusConnection *conn = dbus_gsm_connection(); const char *path = voicecall_build_path(modem, call->call); return g_dbus_unregister_interface(conn, path, VOICECALL_INTERFACE); } static struct voicecalls_data *voicecalls_create() { struct voicecalls_data *calls; calls = g_try_new0(struct voicecalls_data, 1); return calls; } static void voicecalls_destroy(gpointer userdata) { struct ofono_modem *modem = userdata; struct voicecalls_data *calls = modem->voicecalls; GSList *l; for (l = calls->call_list; l; l = l->next) voicecall_dbus_unregister(modem, l->data); g_slist_free(calls->call_list); g_free(calls); modem->voicecalls = 0; } static int voicecalls_path_list(struct ofono_modem *modem, GSList *call_list, char ***objlist) { GSList *l; int i; struct voicecall *v; *objlist = g_new0(char *, g_slist_length(call_list) + 1); if (*objlist == NULL) return -1; for (i = 0, l = call_list; l; l = l->next, i++) { v = l->data; (*objlist)[i] = g_strdup(voicecall_build_path(modem, v->call)); } return 0; } static gboolean voicecalls_have_active(struct voicecalls_data *calls) { GSList *l; struct voicecall *v; for (l = calls->call_list; l; l = l->next) { v = l->data; if (v->call->status == CALL_STATUS_ACTIVE || v->call->status == CALL_STATUS_INCOMING || v->call->status == CALL_STATUS_DIALING || v->call->status == CALL_STATUS_ALERTING) return TRUE; } return FALSE; } static gboolean voicecalls_have_connected(struct voicecalls_data *calls) { GSList *l; struct voicecall *v; for (l = calls->call_list; l; l = l->next) { v = l->data; if (v->call->status == CALL_STATUS_ACTIVE) return TRUE; } return FALSE; } static gboolean voicecalls_have_held(struct voicecalls_data *calls) { GSList *l; struct voicecall *v; for (l = calls->call_list; l; l = l->next) { v = l->data; if (v->call->status == CALL_STATUS_HELD) return TRUE; } return FALSE; } static int voicecalls_num_with_status(struct voicecalls_data *calls, int status) { GSList *l; struct voicecall *v; int num = 0; for (l = calls->call_list; l; l = l->next) { v = l->data; if (v->call->status == status) num += 1; } return num; } static int voicecalls_num_active(struct voicecalls_data *calls) { return voicecalls_num_with_status(calls, CALL_STATUS_ACTIVE); } static int voicecalls_num_held(struct voicecalls_data *calls) { return voicecalls_num_with_status(calls, CALL_STATUS_HELD); } static int voicecalls_num_connecting(struct voicecalls_data *calls) { int r = 0; r += voicecalls_num_with_status(calls, CALL_STATUS_DIALING); r += voicecalls_num_with_status(calls, CALL_STATUS_ALERTING); return r; } static GSList *voicecalls_held_list(struct voicecalls_data *calls) { GSList *l; GSList *r = NULL; struct voicecall *v; for (l = calls->call_list; l; l = l->next) { v = l->data; if (v->call->status == CALL_STATUS_HELD) r = g_slist_prepend(r, v); } if (r) r = g_slist_reverse(r); return r; } /* Intended to be used for multiparty, which cannot be incoming, * alerting or dialing */ static GSList *voicecalls_active_list(struct voicecalls_data *calls) { GSList *l; GSList *r = NULL; struct voicecall *v; for (l = calls->call_list; l; l = l->next) { v = l->data; if (v->call->status == CALL_STATUS_ACTIVE) r = g_slist_prepend(r, v); } if (r) r = g_slist_reverse(r); return r; } static gboolean voicecalls_have_waiting(struct voicecalls_data *calls) { GSList *l; struct voicecall *v; for (l = calls->call_list; l; l = l->next) { v = l->data; if (v->call->status == CALL_STATUS_WAITING) return TRUE; } return FALSE; } static void voicecalls_release_queue(struct ofono_modem *modem, GSList *calls) { struct voicecalls_data *voicecalls = modem->voicecalls; GSList *l; g_slist_free(voicecalls->release_list); voicecalls->release_list = NULL; for (l = calls; l; l = l->next) { voicecalls->release_list = g_slist_prepend(voicecalls->release_list, l->data); } } static void voicecalls_release_next(struct ofono_modem *modem) { struct voicecalls_data *voicecalls = modem->voicecalls; struct voicecall *call; if (!voicecalls->release_list) return; call = voicecalls->release_list->data; voicecalls->release_list = g_slist_remove(voicecalls->release_list, call); voicecalls->ops->release_specific(modem, call->call->id, multirelease_callback, modem); } static DBusMessage *manager_get_properties(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; DBusMessage *reply; DBusMessageIter iter; DBusMessageIter dict; char **callobj_list; reply = dbus_message_new_method_return(msg); if (!reply) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, PROPERTIES_ARRAY_SIGNATURE, &dict); voicecalls_path_list(modem, calls->call_list, &callobj_list); dbus_gsm_dict_append_array(&dict, "Calls", DBUS_TYPE_OBJECT_PATH, &callobj_list); dbus_gsm_free_string_array(callobj_list); voicecalls_path_list(modem, calls->multiparty_list, &callobj_list); dbus_gsm_dict_append_array(&dict, "MultipartyCalls", DBUS_TYPE_OBJECT_PATH, &callobj_list); dbus_gsm_free_string_array(callobj_list); dbus_message_iter_close_container(&iter, &dict); return reply; } static DBusMessage *manager_dial(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; const char *number; int number_type; const char *clirstr; enum ofono_clir_option clir; if (calls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); if (g_slist_length(calls->call_list) >= MAX_VOICE_CALLS) return dbus_gsm_failed(msg); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, DBUS_TYPE_STRING, &clirstr, DBUS_TYPE_INVALID) == FALSE) return dbus_gsm_invalid_args(msg); if (!valid_phone_number_format(number)) return dbus_gsm_invalid_format(msg); if (strlen(clirstr) == 0 || !strcmp(clirstr, "default")) clir = OFONO_CLIR_OPTION_DEFAULT; else if (!strcmp(clirstr, "disabled")) clir = OFONO_CLIR_OPTION_SUPPRESSION; else if (!strcmp(clirstr, "enabled")) clir = OFONO_CLIR_OPTION_INVOCATION; else return dbus_gsm_invalid_format(msg); if (!calls->ops->dial) return dbus_gsm_not_implemented(msg); if (voicecalls_have_active(calls) && voicecalls_have_held(calls)) return dbus_gsm_failed(msg); calls->flags |= VOICECALLS_FLAG_PENDING; calls->pending = dbus_message_ref(msg); string_to_phone_number(number, &number_type, &number); calls->ops->dial(modem, number, number_type, clir, OFONO_CUG_OPTION_DEFAULT, dial_callback, modem); return NULL; } static DBusMessage *manager_transfer(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; int numactive; int numheld; if (calls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); numactive = voicecalls_num_active(calls); /* According to 22.091 section 5.8, the network has the option of * implementing the call transfer operation for a call that is * still dialing/alerting. */ numactive += voicecalls_num_connecting(calls); numheld = voicecalls_num_held(calls); if ((numactive != 1) && (numheld != 1)) return dbus_gsm_failed(msg); if (!calls->ops->transfer) return dbus_gsm_not_implemented(msg); calls->flags |= VOICECALLS_FLAG_PENDING; calls->pending = dbus_message_ref(msg); calls->ops->transfer(modem, generic_callback, calls); return NULL; } static DBusMessage *manager_swap_calls(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; if (calls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); if (voicecalls_have_waiting(calls)) return dbus_gsm_failed(msg); if (!calls->ops->hold_all_active) return dbus_gsm_not_implemented(msg); calls->flags |= VOICECALLS_FLAG_PENDING; calls->pending = dbus_message_ref(msg); calls->ops->hold_all_active(modem, generic_callback, calls); return NULL; } static DBusMessage *manager_release_and_answer(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; if (calls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); if (!voicecalls_have_active(calls) || !voicecalls_have_waiting(calls)) return dbus_gsm_failed(msg); if (!calls->ops->release_all_active) return dbus_gsm_not_implemented(msg); calls->flags |= VOICECALLS_FLAG_PENDING; calls->pending = dbus_message_ref(msg); calls->ops->release_all_active(modem, generic_callback, calls); return NULL; } static DBusMessage *manager_hold_and_answer(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; if (calls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); if (voicecalls_have_active(calls) && voicecalls_have_held(calls) && voicecalls_have_waiting(calls)) return dbus_gsm_failed(msg); if (!calls->ops->hold_all_active) return dbus_gsm_not_implemented(msg); calls->flags |= VOICECALLS_FLAG_PENDING; calls->pending = dbus_message_ref(msg); calls->ops->hold_all_active(modem, generic_callback, calls); return NULL; } static DBusMessage *manager_hangup_all(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; if (calls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); if (!calls->ops->release_specific) return dbus_gsm_not_implemented(msg); if (g_slist_length(calls->call_list) == 0) { DBusMessage *reply = dbus_message_new_method_return(msg); return reply; } calls->flags |= VOICECALLS_FLAG_PENDING; calls->flags |= VOICECALLS_FLAG_MULTI_RELEASE; calls->pending = dbus_message_ref(msg); voicecalls_release_queue(modem, calls->call_list); voicecalls_release_next(modem); return NULL; } static DBusMessage *multiparty_private_chat(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; const char *callpath; const char *c; unsigned int id; GSList *l; if (calls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &callpath, DBUS_TYPE_INVALID) == FALSE) return dbus_gsm_invalid_args(msg); if (strlen(callpath) == 0 || strlen(callpath) > MAX_DBUS_PATH_LEN) return dbus_gsm_invalid_format(msg); c = strrchr(callpath, '/'); if (!c || strncmp(modem->path, callpath, c-callpath)) return dbus_gsm_not_found(msg); if (!sscanf(c, "/voicecall%2u", &id)) return dbus_gsm_not_found(msg); for (l = calls->multiparty_list; l; l = l->next) { struct voicecall *v = l->data; if (v->call->id == id) break; } if (!l) return dbus_gsm_not_found(msg); /* If we found id on the list of multiparty calls, then by definition * the multiparty call exists. Only thing to check is whether we have * held calls */ if (voicecalls_have_held(calls)) return dbus_gsm_failed(msg); if (!calls->ops->private_chat) return dbus_gsm_not_implemented(msg); calls->flags |= VOICECALLS_FLAG_PENDING; calls->pending = dbus_message_ref(msg); calls->ops->private_chat(modem, id, private_chat_callback, modem); return NULL; } static DBusMessage *multiparty_create(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; if (calls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); if (!voicecalls_have_held(calls) || !voicecalls_have_active(calls)) return dbus_gsm_failed(msg); if (!calls->ops->create_multiparty) return dbus_gsm_not_implemented(msg); calls->flags |= VOICECALLS_FLAG_PENDING; calls->pending = dbus_message_ref(msg); calls->ops->create_multiparty(modem, multiparty_create_callback, modem); return NULL; } static DBusMessage *multiparty_hangup(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; if (calls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); if (!calls->ops->release_specific) return dbus_gsm_not_implemented(msg); if (g_slist_length(calls->multiparty_list) == 0) { DBusMessage *reply = dbus_message_new_method_return(msg); return reply; } calls->flags |= VOICECALLS_FLAG_PENDING; calls->pending = dbus_message_ref(msg); /* We have waiting calls, can't use +CHLD to release */ if (voicecalls_have_waiting(calls)) { calls->flags |= VOICECALLS_FLAG_MULTI_RELEASE; voicecalls_release_queue(modem, calls->multiparty_list); voicecalls_release_next(modem); } else { struct ofono_call *v = calls->multiparty_list->data; if (v->status == CALL_STATUS_HELD) calls->ops->release_all_held(modem, generic_callback, calls); else calls->ops->release_all_active(modem, generic_callback, calls); } return NULL; } static DBusMessage *manager_tone(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; const char *in_tones; char *tones; int i, len; if (calls->flags & VOICECALLS_FLAG_PENDING) return dbus_gsm_busy(msg); if (!calls->ops->send_tones) return dbus_gsm_not_implemented(msg); /* Send DTMFs only if we have at least one connected call */ if (!voicecalls_have_connected(calls)) return dbus_gsm_failed(msg); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &in_tones, DBUS_TYPE_INVALID) == FALSE) return dbus_gsm_invalid_args(msg); len = strlen(in_tones); if (len == 0) return dbus_gsm_invalid_format(msg); tones = g_ascii_strup(in_tones, len); /* Tones can be 0-9, *, #, A-D according to 27.007 C.2.11 */ for (i = 0; i < len; i++) { if (g_ascii_isdigit(tones[i]) || tones[i] == '*' || tones[i] == '#' || (tones[i] >= 'A' && tones[i] <= 'D')) continue; g_free(tones); return dbus_gsm_invalid_format(msg); } calls->flags |= VOICECALLS_FLAG_PENDING; calls->pending = dbus_message_ref(msg); calls->ops->send_tones(modem, tones, generic_callback, calls); g_free(tones); return NULL; } static GDBusMethodTable manager_methods[] = { { "GetProperties", "", "a{sv}", manager_get_properties }, { "Dial", "ss", "o", manager_dial, G_DBUS_METHOD_FLAG_ASYNC }, { "Transfer", "", "", manager_transfer, G_DBUS_METHOD_FLAG_ASYNC }, { "SwapCalls", "", "", manager_swap_calls, G_DBUS_METHOD_FLAG_ASYNC }, { "ReleaseAndAnswer", "", "", manager_release_and_answer, G_DBUS_METHOD_FLAG_ASYNC }, { "HoldAndAnswer", "", "", manager_hold_and_answer, G_DBUS_METHOD_FLAG_ASYNC }, { "HangupAll", "", "", manager_hangup_all, G_DBUS_METHOD_FLAG_ASYNC }, { "PrivateChat", "o", "ao", multiparty_private_chat, G_DBUS_METHOD_FLAG_ASYNC }, { "CreateMultiparty", "", "ao", multiparty_create, G_DBUS_METHOD_FLAG_ASYNC }, { "HangupMultiparty", "", "", multiparty_hangup, G_DBUS_METHOD_FLAG_ASYNC }, { "SendTones", "s", "", manager_tone, G_DBUS_METHOD_FLAG_ASYNC }, { } }; static GDBusSignalTable manager_signals[] = { { "PropertyChanged", "sv" }, { } }; static gboolean real_emit_call_list_changed(void *data) { struct ofono_modem *modem = data; struct voicecalls_data *voicecalls = modem->voicecalls; DBusConnection *conn = dbus_gsm_connection(); char **objpath_list; voicecalls_path_list(modem, voicecalls->call_list, &objpath_list); dbus_gsm_signal_array_property_changed(conn, modem->path, VOICECALL_MANAGER_INTERFACE, "Calls", DBUS_TYPE_OBJECT_PATH, &objpath_list); dbus_gsm_free_string_array(objpath_list); ofono_debug("Resetting updating flag"); voicecalls->flags &= ~VOICECALLS_FLAG_UPDATING_CALL_LIST; return FALSE; } static void emit_call_list_changed(struct ofono_modem *modem) { //struct voicecalls_data *calls = modem->voicecalls; #ifdef DELAY_EMIT if (!(calls->flags & VOICECALLS_FLAG_UPDATING_CALL_LIST)) { calls->flags |= VOICECALLS_FLAG_UPDATING_CALL_LIST; g_timeout_add(0, real_emit_call_list_changed, modem); } #else real_emit_call_list_changed(modem); #endif } static gboolean real_emit_multiparty_call_list_changed(void *data) { struct ofono_modem *modem = data; struct voicecalls_data *voicecalls = modem->voicecalls; DBusConnection *conn = dbus_gsm_connection(); char **objpath_list; voicecalls_path_list(modem, voicecalls->multiparty_list, &objpath_list); dbus_gsm_signal_array_property_changed(conn, modem->path, VOICECALL_MANAGER_INTERFACE, "MultipartyCalls", DBUS_TYPE_OBJECT_PATH, &objpath_list); dbus_gsm_free_string_array(objpath_list); voicecalls->flags &= ~VOICECALLS_FLAG_UPDATING_MPTY_CALL_LIST; return FALSE; } static void emit_multiparty_call_list_changed(struct ofono_modem *modem) { //struct voicecalls_data *calls = modem->voicecalls; #ifdef DELAY_EMIT if (!(calls->flags & VOICECALLS_FLAG_UPDATING_MPTY_CALL_LIST)) { calls->flags |= VOICECALLS_FLAG_UPDATING_MPTY_CALL_LIST; g_timeout_add(0, real_emit_multiparty_call_list_changed, modem); } #else real_emit_multiparty_call_list_changed(modem); #endif } void ofono_voicecall_disconnected(struct ofono_modem *modem, int id, enum ofono_disconnect_reason reason, const struct ofono_error *error) { GSList *l; struct voicecalls_data *calls = modem->voicecalls; struct voicecall *call; ofono_debug("Got disconnection event for id: %d, reason: %d", id, reason); l = g_slist_find_custom(calls->call_list, GINT_TO_POINTER(id), call_compare_by_id); if (!l) { ofono_error("Plugin notified us of call disconnect for" " unknown call"); return; } call = l->data; l = g_slist_find_custom(calls->multiparty_list, GINT_TO_POINTER(id), call_compare_by_id); if (l) { calls->multiparty_list = g_slist_remove(calls->multiparty_list, call); if (calls->multiparty_list->next == NULL) { /* Size == 1 */ g_slist_free(calls->multiparty_list); calls->multiparty_list = 0; } emit_multiparty_call_list_changed(modem); } calls->release_list = g_slist_remove(calls->release_list, call); modem_release_callid(modem, id); /* TODO: Emit disconnect reason */ voicecall_set_call_status(modem, call, CALL_STATUS_DISCONNECTED); voicecall_dbus_unregister(modem, call); calls->call_list = g_slist_remove(calls->call_list, call); emit_call_list_changed(modem); } void ofono_voicecall_notify(struct ofono_modem *modem, const struct ofono_call *call) { GSList *l; struct voicecalls_data *calls = modem->voicecalls; struct voicecall *v; struct ofono_call *newcall = NULL; //const char *member; ofono_debug("Got a voicecall event, status: %d, id: %u, number: %s", call->status, call->id, call->phone_number); l = g_slist_find_custom(calls->call_list, GINT_TO_POINTER(call->id), call_compare_by_id); if (l) { ofono_debug("Found call with id: %d\n", call->id); voicecall_set_call_status(modem, l->data, call->status); voicecall_set_call_lineid(modem, l->data, call->phone_number, call->number_type, call->clip_validity); return; } ofono_debug("Did not find a call with id: %d\n", call->id); newcall = g_try_new0(struct ofono_call, 1); if (!call) { ofono_error("Unable to allocate call"); goto err; } memcpy(newcall, call, sizeof(struct ofono_call)); if (modem_alloc_callid(modem) != call->id) { ofono_error("Warning: Call id and internally tracked id" " do not correspond"); goto err; } v = voicecall_create(modem, newcall); if (!v) { ofono_error("Unable to allocate voicecall_data"); goto err; } if (!voicecall_dbus_register(v)) { ofono_error("Unable to register voice call"); goto err; } calls->call_list = g_slist_insert_sorted(calls->call_list, v, call_compare); emit_call_list_changed(modem); return; err: if (newcall) g_free(newcall); if (v) g_free(v); } void ofono_voicecall_cssi(struct ofono_modem *modem, int code, int index) { } void ofono_voicecall_cssu(struct ofono_modem *modem, int code, int index, const char *number, int number_type) { } static void generic_callback(const struct ofono_error *error, void *data) { struct voicecalls_data *calls = data; DBusConnection *conn = dbus_gsm_connection(); DBusMessage *reply; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) ofono_debug("command failed with error: %s", telephony_error_to_str(error)); calls->flags &= ~VOICECALLS_FLAG_PENDING; if (!calls->pending) return; if (error->type == OFONO_ERROR_TYPE_NO_ERROR) reply = dbus_message_new_method_return(calls->pending); else reply = dbus_gsm_failed(calls->pending); g_dbus_send_message(conn, reply); dbus_message_unref(calls->pending); calls->pending = NULL; } static void multirelease_callback(const struct ofono_error *error, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; DBusConnection *conn = dbus_gsm_connection(); DBusMessage *reply; if (g_slist_length(calls->release_list)) { voicecalls_release_next(modem); return; } calls->flags &= ~VOICECALLS_FLAG_MULTI_RELEASE; calls->flags &= ~VOICECALLS_FLAG_PENDING; if (!calls->pending) return; reply = dbus_message_new_method_return(calls->pending); g_dbus_send_message(conn, reply); dbus_message_unref(calls->pending); calls->pending = NULL; } static struct ofono_call *synthesize_outgoing_call(struct ofono_modem *modem, DBusMessage *msg) { const char *number; int number_type; struct ofono_call *call; call = g_try_new0(struct ofono_call, 1); if (!call) return call; if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, DBUS_TYPE_INVALID) == FALSE) number = ""; else string_to_phone_number(number, &number_type, &number); call->id = modem_alloc_callid(modem); if (call->id == 0) { ofono_error("Failed to alloc callid, too many calls"); g_free(call); return NULL; } call->direction = CALL_DIRECTION_MOBILE_ORIGINATED; call->status = CALL_STATUS_DIALING; strcpy(call->phone_number, number); call->number_type = number_type; call->clip_validity = CLIP_VALIDITY_VALID; return call; } static void dial_callback(const struct ofono_error *error, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; DBusConnection *conn = dbus_gsm_connection(); DBusMessage *reply; GSList *l; struct ofono_call *call; const char *path; gboolean need_to_emit = FALSE; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) ofono_debug("Dial callback returned error: %s", telephony_error_to_str(error)); calls->flags &= ~VOICECALLS_FLAG_PENDING; if (!calls->pending) return; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { reply = dbus_gsm_failed(calls->pending); g_dbus_send_message(conn, reply); goto out; } reply = dbus_message_new_method_return(calls->pending); if (!reply) goto out; /* Two things can happen, the call notification arrived before dial * callback or dial callback was first. Handle here */ for (l = calls->call_list; l; l = l->next) { struct voicecall *v = l->data; if (v->call->status == CALL_STATUS_DIALING || v->call->status == CALL_STATUS_ALERTING) break; } if (!l) { struct voicecall *v; call = synthesize_outgoing_call(modem, calls->pending); if (!call) { reply = dbus_gsm_failed(calls->pending); g_dbus_send_message(conn, reply); goto out; } v = voicecall_create(modem, call); if (!v) { reply = dbus_gsm_failed(calls->pending); g_dbus_send_message(conn, reply); goto out; } ofono_debug("Registering new call: %d", call->id); voicecall_dbus_register(voicecall_create(modem, call)); calls->call_list = g_slist_insert_sorted(calls->call_list, v, call_compare); need_to_emit = TRUE; } else { struct voicecall *v = l->data; call = v->call; } path = voicecall_build_path(modem, call); dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); g_dbus_send_message(conn, reply); if (need_to_emit) emit_call_list_changed(modem); out: dbus_message_unref(calls->pending); calls->pending = NULL; } static void multiparty_callback_common(struct ofono_modem *modem, DBusMessage *reply) { struct voicecalls_data *voicecalls = modem->voicecalls; DBusMessageIter iter; DBusMessageIter array_iter; char **objpath_list; int i; voicecalls_path_list(modem, voicecalls->multiparty_list, &objpath_list); dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH_AS_STRING, &array_iter); for (i = 0; objpath_list[i]; i++) dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_OBJECT_PATH, &objpath_list[i]); dbus_message_iter_close_container(&iter, &array_iter); } static void multiparty_create_callback(const struct ofono_error *error, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; DBusConnection *conn = dbus_gsm_connection(); DBusMessage *reply; gboolean need_to_emit = FALSE; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) ofono_debug("command failed with error: %s", telephony_error_to_str(error)); calls->flags &= ~VOICECALLS_FLAG_PENDING; if (!calls->pending) return; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { reply = dbus_gsm_failed(calls->pending); goto out; } /* We just created a multiparty call, gather all held * active calls and add them to the multiparty list */ if (calls->multiparty_list) { g_slist_free(calls->multiparty_list); calls->multiparty_list = 0; } calls->multiparty_list = g_slist_concat(calls->multiparty_list, voicecalls_held_list(calls)); calls->multiparty_list = g_slist_concat(calls->multiparty_list, voicecalls_active_list(calls)); calls->multiparty_list = g_slist_sort(calls->multiparty_list, call_compare); if (g_slist_length(calls->multiparty_list) < 2) { ofono_error("Created multiparty call, but size is less than 2" " panic!"); reply = dbus_gsm_failed(calls->pending); } else { reply = dbus_message_new_method_return(calls->pending); multiparty_callback_common(modem, reply); need_to_emit = TRUE; } out: g_dbus_send_message(conn, reply); if (need_to_emit) emit_multiparty_call_list_changed(modem); dbus_message_unref(calls->pending); calls->pending = NULL; } static void private_chat_callback(const struct ofono_error *error, void *data) { struct ofono_modem *modem = data; struct voicecalls_data *calls = modem->voicecalls; DBusConnection *conn = dbus_gsm_connection(); DBusMessage *reply; gboolean need_to_emit = FALSE; const char *callpath; const char *c; int id; GSList *l; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) ofono_debug("command failed with error: %s", telephony_error_to_str(error)); calls->flags &= ~VOICECALLS_FLAG_PENDING; if (!calls->pending) return; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { reply = dbus_gsm_failed(calls->pending); goto out; } dbus_message_get_args(calls->pending, NULL, DBUS_TYPE_OBJECT_PATH, &callpath, DBUS_TYPE_INVALID); c = strrchr(callpath, '/'); sscanf(c, "/voicecall%2u", &id); l = g_slist_find_custom(calls->multiparty_list, GINT_TO_POINTER(id), call_compare_by_id); if (l) { calls->multiparty_list = g_slist_remove(calls->multiparty_list, l->data); if (g_slist_length(calls->multiparty_list) < 2) { g_slist_free(calls->multiparty_list); calls->multiparty_list = 0; } } reply = dbus_message_new_method_return(calls->pending); multiparty_callback_common(modem, reply); need_to_emit = TRUE; out: g_dbus_send_message(conn, reply); if (need_to_emit) emit_multiparty_call_list_changed(modem); dbus_message_unref(calls->pending); calls->pending = NULL; } int ofono_voicecall_register(struct ofono_modem *modem, struct ofono_voicecall_ops *ops) { DBusConnection *conn = dbus_gsm_connection(); if (modem == NULL) return -1; if (ops == NULL) return -1; modem->voicecalls = voicecalls_create(); if (modem->voicecalls == NULL) return -1; modem->voicecalls->ops = ops; if (!g_dbus_register_interface(conn, modem->path, VOICECALL_MANAGER_INTERFACE, manager_methods, manager_signals, NULL, modem, voicecalls_destroy)) { ofono_error("Could not create %s interface", VOICECALL_MANAGER_INTERFACE); voicecalls_destroy(modem->voicecalls); return -1; } modem_add_interface(modem, VOICECALL_MANAGER_INTERFACE); return 0; } void ofono_voicecall_unregister(struct ofono_modem *modem) { DBusConnection *conn = dbus_gsm_connection(); if (!modem->voicecalls) return; modem_remove_interface(modem, VOICECALL_MANAGER_INTERFACE); g_dbus_unregister_interface(conn, modem->path, VOICECALL_MANAGER_INTERFACE); modem->voicecalls = NULL; }