/* * * oFono - Open Source Telephony * * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. * Copyright (C) 2010 ST-Ericsson AB. * * 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 #include #include "gatchat.h" #include "gatresult.h" #include "common.h" #include "stemodem.h" enum call_status_ste { STE_CALL_STATUS_IDLE = 0, STE_CALL_STATUS_CALLING = 1, STE_CALL_STATUS_CONNECTING = 2, STE_CALL_STATUS_ACTIVE = 3, STE_CALL_STATUS_HOLD = 4, STE_CALL_STATUS_WAITING = 5, STE_CALL_STATUS_ALERTING = 6, STE_CALL_STATUS_BUSY = 7, STE_CALL_STATUS_RELEASED = 8, }; static const char *none_prefix[] = { NULL }; struct voicecall_data { GSList *calls; unsigned int local_release; GAtChat *chat; }; struct release_id_req { struct ofono_voicecall *vc; ofono_voicecall_cb_t cb; void *data; int id; }; struct change_state_req { struct ofono_voicecall *vc; ofono_voicecall_cb_t cb; void *data; int affected_types; }; /* Translate from the ECAV-based STE-status to CLCC based status */ static int call_status_ste_to_ofono(enum call_status_ste status) { switch (status) { case STE_CALL_STATUS_IDLE: case STE_CALL_STATUS_RELEASED: return CALL_STATUS_DISCONNECTED; case STE_CALL_STATUS_CALLING: return CALL_STATUS_DIALING; case STE_CALL_STATUS_CONNECTING: return CALL_STATUS_ALERTING; case STE_CALL_STATUS_ACTIVE: return CALL_STATUS_ACTIVE; case STE_CALL_STATUS_HOLD: return CALL_STATUS_HELD; case STE_CALL_STATUS_WAITING: return CALL_STATUS_WAITING; case STE_CALL_STATUS_ALERTING: return CALL_STATUS_INCOMING; case STE_CALL_STATUS_BUSY: return CALL_STATUS_DISCONNECTED; } return CALL_STATUS_DISCONNECTED; } static struct ofono_call *create_call(struct ofono_voicecall *vc, int type, int direction, int status, const char *num, int num_type, int clip) { struct voicecall_data *d = ofono_voicecall_get_data(vc); struct ofono_call *call; /* Generate a call structure for the waiting call */ call = g_try_new(struct ofono_call, 1); if (call == NULL) return NULL; ofono_call_init(call); call->type = type; call->direction = direction; call->status = status; if (clip != CLIP_VALIDITY_NOT_AVAILABLE) { strncpy(call->phone_number.number, num, OFONO_MAX_PHONE_NUMBER_LENGTH); call->phone_number.type = num_type; } call->clip_validity = clip; d->calls = g_slist_insert_sorted(d->calls, call, at_util_call_compare); return call; } static void ste_generic_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct change_state_req *req = user_data; struct voicecall_data *vd = ofono_voicecall_get_data(req->vc); struct ofono_error error; decode_at_error(&error, g_at_result_final_response(result)); if (ok && req->affected_types) { GSList *l; struct ofono_call *call; for (l = vd->calls; l; l = l->next) { call = l->data; if (req->affected_types & (1 << call->status)) vd->local_release |= (1 << call->id); } } req->cb(&error, req->data); } static void release_id_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct release_id_req *req = user_data; struct voicecall_data *vd = ofono_voicecall_get_data(req->vc); struct ofono_error error; decode_at_error(&error, g_at_result_final_response(result)); if (ok) vd->local_release = 1 << req->id; req->cb(&error, req->data); } static void atd_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; struct ofono_error error; ofono_voicecall_cb_t cb = cbd->cb; decode_at_error(&error, g_at_result_final_response(result)); cb(&error, cbd->data); } static void ste_dial(struct ofono_voicecall *vc, const struct ofono_phone_number *ph, enum ofono_clir_option clir, ofono_voicecall_cb_t cb, void *data) { struct voicecall_data *vd = ofono_voicecall_get_data(vc); struct cb_data *cbd = cb_data_new(cb, data); char buf[256]; cbd->user = vc; if (ph->type == 145) snprintf(buf, sizeof(buf), "ATD+%s", ph->number); else snprintf(buf, sizeof(buf), "ATD%s", ph->number); switch (clir) { case OFONO_CLIR_OPTION_DEFAULT: break; case OFONO_CLIR_OPTION_INVOCATION: strcat(buf, "I"); break; case OFONO_CLIR_OPTION_SUPPRESSION: strcat(buf, "i"); break; } strcat(buf, ";"); if (g_at_chat_send(vd->chat, buf, none_prefix, atd_cb, cbd, g_free) > 0) return; g_free(cbd); CALLBACK_WITH_FAILURE(cb, data); } static void ste_template(const char *cmd, struct ofono_voicecall *vc, GAtResultFunc result_cb, unsigned int affected_types, ofono_voicecall_cb_t cb, void *data) { struct voicecall_data *vd = ofono_voicecall_get_data(vc); struct change_state_req *req = g_try_new0(struct change_state_req, 1); if (req == NULL) goto error; req->vc = vc; req->cb = cb; req->data = data; req->affected_types = affected_types; if (g_at_chat_send(vd->chat, cmd, none_prefix, result_cb, req, g_free) > 0) return; error: g_free(req); CALLBACK_WITH_FAILURE(cb, data); } static void ste_answer(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, void *data) { ste_template("ATA", vc, ste_generic_cb, 0, cb, data); } static void ste_hangup(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, void *data) { unsigned int active_dial_alert_or_incoming = (1 << CALL_STATUS_ACTIVE) | (1 << CALL_STATUS_DIALING) | (1 << CALL_STATUS_ALERTING) | (1 << CALL_STATUS_INCOMING); ste_template("AT+CHUP", vc, ste_generic_cb, active_dial_alert_or_incoming, cb, data); } static void ste_hold_all_active(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, void *data) { ste_template("AT+CHLD=2", vc, ste_generic_cb, 0, cb, data); } static void ste_release_all_held(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, void *data) { unsigned int held = 1 << CALL_STATUS_HELD; ste_template("AT+CHLD=0", vc, ste_generic_cb, held, cb, data); } static void ste_set_udub(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, void *data) { unsigned int incoming_or_waiting = (1 << CALL_STATUS_INCOMING) | (1 << CALL_STATUS_WAITING); ste_template("AT+CHLD=0", vc, ste_generic_cb, incoming_or_waiting, cb, data); } static void ste_release_all_active(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, void *data) { unsigned int active = 1 << CALL_STATUS_ACTIVE; ste_template("AT+CHLD=1", vc, ste_generic_cb, active, cb, data); } static void ste_release_specific(struct ofono_voicecall *vc, int id, ofono_voicecall_cb_t cb, void *data) { struct voicecall_data *vd = ofono_voicecall_get_data(vc); struct release_id_req *req = g_try_new0(struct release_id_req, 1); char buf[32]; if (req == NULL) goto error; req->vc = vc; req->cb = cb; req->data = data; req->id = id; snprintf(buf, sizeof(buf), "AT+CHLD=1%d", id); if (g_at_chat_send(vd->chat, buf, none_prefix, release_id_cb, req, g_free) > 0) return; error: g_free(req); CALLBACK_WITH_FAILURE(cb, data); } static void ste_private_chat(struct ofono_voicecall *vc, int id, ofono_voicecall_cb_t cb, void *data) { char buf[32]; snprintf(buf, sizeof(buf), "AT+CHLD=2%d", id); ste_template(buf, vc, ste_generic_cb, 0, cb, data); } static void ste_create_multiparty(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, void *data) { ste_template("AT+CHLD=3", vc, ste_generic_cb, 0, cb, data); } static void ste_transfer(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, void *data) { /* Held & Active */ unsigned int transfer = 0x1 | 0x2; /* Transfer can puts held & active calls together and disconnects * from both. However, some networks support transferring of * dialing/ringing calls as well. */ transfer |= 0x4 | 0x8; ste_template("AT+CHLD=4", vc, ste_generic_cb, transfer, cb, data); } static void ste_deflect(struct ofono_voicecall *vc, const struct ofono_phone_number *ph, ofono_voicecall_cb_t cb, void *data) { char buf[128]; unsigned int incoming_or_waiting = (1 << CALL_STATUS_INCOMING) | (1 << CALL_STATUS_WAITING); snprintf(buf, sizeof(buf), "AT+CTFR=\"%s\",%d", ph->number, ph->type); ste_template(buf, vc, ste_generic_cb, incoming_or_waiting, cb, data); } static void vts_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; ofono_voicecall_cb_t cb = cbd->cb; struct ofono_error error; decode_at_error(&error, g_at_result_final_response(result)); cb(&error, cbd->data); } static void ste_send_dtmf(struct ofono_voicecall *vc, const char *dtmf, ofono_voicecall_cb_t cb, void *data) { struct voicecall_data *vd = ofono_voicecall_get_data(vc); struct cb_data *cbd = cb_data_new(cb, data); int s; char *buf; /* strlen("AT+VTS=) = 7 + NULL */ buf = g_try_new(char, strlen(dtmf) + 8); if (buf == NULL) goto error; sprintf(buf, "AT+VTS=%s", dtmf); s = g_at_chat_send(vd->chat, buf, none_prefix, vts_cb, cbd, g_free); g_free(buf); if (s > 0) return; error: g_free(cbd); CALLBACK_WITH_FAILURE(cb, data); } static void ecav_notify(GAtResult *result, gpointer user_data) { struct ofono_voicecall *vc = user_data; struct voicecall_data *vd = ofono_voicecall_get_data(vc); GAtResultIter iter; const char *num; int id; int status; int call_type; int num_type; struct ofono_call *new_call; struct ofono_call *existing_call = NULL; GSList *l; /* Parse ECAV */ g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "*ECAV:")) return; if (!g_at_result_iter_next_number(&iter, &id)) return; if (!g_at_result_iter_next_number(&iter, &status)) return; if (!g_at_result_iter_next_number(&iter, &call_type)) return; if (call_type != BEARER_CLASS_VOICE) return; /* Skip process id and exit cause */ g_at_result_iter_skip_next(&iter); g_at_result_iter_skip_next(&iter); status = call_status_ste_to_ofono(status); if (status == CALL_STATUS_DIALING || status == CALL_STATUS_WAITING || status == CALL_STATUS_INCOMING) { /* * If caller uses hidden id, the number and * number type might not be present. Don't * look for type if number is not present. */ if (!g_at_result_iter_next_string(&iter, &num)) { num = ""; num_type = 128; } else if (!g_at_result_iter_next_number(&iter, &num_type)) return; } /* * Handle the call according to the status. * If it doesn't exists we make a new one */ l = g_slist_find_custom(vd->calls, GUINT_TO_POINTER(id), at_util_call_compare_by_id); if (l) existing_call = l->data; if (l == NULL && status != CALL_STATUS_DIALING && status != CALL_STATUS_WAITING && status != CALL_STATUS_INCOMING) { ofono_error("ECAV notification for unknown call." " id: %d, status: %d", id, status); return; } switch (status) { case CALL_STATUS_DISCONNECTED: { enum ofono_disconnect_reason reason; existing_call->status = status; if (vd->local_release & (1 << existing_call->id)) reason = OFONO_DISCONNECT_REASON_LOCAL_HANGUP; else reason = OFONO_DISCONNECT_REASON_REMOTE_HANGUP; ofono_voicecall_disconnected(vc, existing_call->id, reason, NULL); vd->local_release &= ~(1 << existing_call->id); vd->calls = g_slist_remove(vd->calls, l->data); g_free(existing_call); break; } case CALL_STATUS_DIALING: case CALL_STATUS_WAITING: case CALL_STATUS_INCOMING: { int clip_validity; int direction; if (status == CALL_STATUS_DIALING) direction = CALL_DIRECTION_MOBILE_ORIGINATED; else direction = CALL_DIRECTION_MOBILE_TERMINATED; if (strlen(num) > 0) clip_validity = CLIP_VALIDITY_VALID; else clip_validity = CLIP_VALIDITY_NOT_AVAILABLE; new_call = create_call(vc, call_type, direction, status, num, num_type, clip_validity); if (new_call == NULL) { ofono_error("Unable to malloc. " "Call management is fubar"); return; } new_call->id = id; ofono_voicecall_notify(vc, new_call); break; } case CALL_STATUS_ALERTING: case CALL_STATUS_ACTIVE: case CALL_STATUS_HELD: existing_call->status = status; ofono_voicecall_notify(vc, existing_call); break; } } static void ste_voicecall_initialized(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_voicecall *vc = user_data; struct voicecall_data *vd = ofono_voicecall_get_data(vc); if (!ok) { ofono_error("*ECAV not enabled. " "Do not have proper call handling"); ofono_voicecall_remove(vc); return; } g_at_chat_register(vd->chat, "*ECAV:", ecav_notify, FALSE, vc, NULL); ofono_voicecall_register(vc); } static int ste_voicecall_probe(struct ofono_voicecall *vc, unsigned int vendor, void *data) { GAtChat *chat = data; struct voicecall_data *vd; vd = g_try_new0(struct voicecall_data, 1); if (vd == NULL) return -ENOMEM; vd->chat = g_at_chat_clone(chat); ofono_voicecall_set_data(vc, vd); g_at_chat_send(vd->chat, "AT*ECAM=2", none_prefix, ste_voicecall_initialized, vc, NULL); return 0; } static void ste_voicecall_remove(struct ofono_voicecall *vc) { struct voicecall_data *vd = ofono_voicecall_get_data(vc); g_slist_free_full(vd->calls, g_free); ofono_voicecall_set_data(vc, NULL); g_at_chat_unref(vd->chat); g_free(vd); } static const struct ofono_voicecall_driver driver = { .name = "stemodem", .probe = ste_voicecall_probe, .remove = ste_voicecall_remove, .dial = ste_dial, .answer = ste_answer, .hangup_active = ste_hangup, .hold_all_active = ste_hold_all_active, .release_all_held = ste_release_all_held, .set_udub = ste_set_udub, .release_all_active = ste_release_all_active, .release_specific = ste_release_specific, .private_chat = ste_private_chat, .create_multiparty = ste_create_multiparty, .transfer = ste_transfer, .deflect = ste_deflect, .swap_without_accept = NULL, .send_tones = ste_send_dtmf }; void ste_voicecall_init(void) { ofono_voicecall_driver_register(&driver); } void ste_voicecall_exit(void) { ofono_voicecall_driver_unregister(&driver); }