/* * * oFono - Open Source Telephony * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * 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 "ofono.h" #include "common.h" static GSList *g_drivers; struct ofono_cdma_voicecall { struct ofono_cdma_phone_number phone_number; struct ofono_cdma_phone_number waiting_number; int direction; enum cdma_call_status status; time_t start_time; DBusMessage *pending; const struct ofono_cdma_voicecall_driver *driver; void *driver_data; struct ofono_atom *atom; }; static const char *disconnect_reason_to_string(enum ofono_disconnect_reason r) { switch (r) { case OFONO_DISCONNECT_REASON_LOCAL_HANGUP: return "local"; case OFONO_DISCONNECT_REASON_REMOTE_HANGUP: return "remote"; default: return "network"; } } static const char *cdma_call_status_to_string(enum cdma_call_status status) { switch (status) { case CDMA_CALL_STATUS_ACTIVE: return "active"; case CDMA_CALL_STATUS_DIALING: return "dialing"; case CDMA_CALL_STATUS_ALERTING: return "alerting"; case CDMA_CALL_STATUS_INCOMING: return "incoming"; case CDMA_CALL_STATUS_DISCONNECTED: return "disconnected"; } return NULL; } static const char *time_to_str(const time_t *t) { static char buf[128]; struct tm tm; strftime(buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime_r(t, &tm)); buf[127] = '\0'; return buf; } static void generic_callback(const struct ofono_error *error, void *data) { struct ofono_cdma_voicecall *vc = data; DBusMessage *reply; if (error->type == OFONO_ERROR_TYPE_NO_ERROR) reply = dbus_message_new_method_return(vc->pending); else reply = __ofono_error_failed(vc->pending); __ofono_dbus_pending_reply(&vc->pending, reply); } static void append_voicecall_properties(struct ofono_cdma_voicecall *vc, DBusMessageIter *dict) { const char *status; const char *lineid; const char *waiting_call; dbus_bool_t call_waiting = FALSE; status = cdma_call_status_to_string(vc->status); ofono_dbus_dict_append(dict, "State", DBUS_TYPE_STRING, &status); lineid = cdma_phone_number_to_string(&vc->phone_number); ofono_dbus_dict_append(dict, "LineIdentification", DBUS_TYPE_STRING, &lineid); if (vc->waiting_number.number[0] != '\0') { waiting_call = cdma_phone_number_to_string(&vc->waiting_number); ofono_dbus_dict_append(dict, "CallWaitingNumber", DBUS_TYPE_STRING, &waiting_call); call_waiting = TRUE; } ofono_dbus_dict_append(dict, "CallWaiting", DBUS_TYPE_BOOLEAN, &call_waiting); if (vc->status == CDMA_CALL_STATUS_ACTIVE) { const char *timestr = time_to_str(&vc->start_time); ofono_dbus_dict_append(dict, "StartTime", DBUS_TYPE_STRING, ×tr); } } static DBusMessage *voicecall_manager_get_properties(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_cdma_voicecall *vc = data; DBusMessage *reply; DBusMessageIter iter; DBusMessageIter dict; reply = dbus_message_new_method_return(msg); if (reply == NULL) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, OFONO_PROPERTIES_ARRAY_SIGNATURE, &dict); append_voicecall_properties(vc, &dict); dbus_message_iter_close_container(&iter, &dict); return reply; } static void voicecall_emit_disconnect_reason(struct ofono_cdma_voicecall *vc, enum ofono_disconnect_reason reason) { DBusConnection *conn = ofono_dbus_get_connection(); const char *path = __ofono_atom_get_path(vc->atom); const char *reason_str; reason_str = disconnect_reason_to_string(reason); g_dbus_emit_signal(conn, path, OFONO_CDMA_VOICECALL_MANAGER_INTERFACE, "DisconnectReason", DBUS_TYPE_STRING, &reason_str, DBUS_TYPE_INVALID); } static void voicecall_set_call_status(struct ofono_cdma_voicecall *vc, enum cdma_call_status status) { DBusConnection *conn = ofono_dbus_get_connection(); const char *path = __ofono_atom_get_path(vc->atom); const char *status_str; enum cdma_call_status old_status; DBG("status: %s", cdma_call_status_to_string(status)); if (vc->status == status) return; old_status = vc->status; vc->status = status; status_str = cdma_call_status_to_string(status); ofono_dbus_signal_property_changed(conn, path, OFONO_CDMA_VOICECALL_MANAGER_INTERFACE, "State", DBUS_TYPE_STRING, &status_str); if (status == CDMA_CALL_STATUS_ACTIVE && old_status == CDMA_CALL_STATUS_DIALING) { const char *timestr; vc->start_time = time(NULL); timestr = time_to_str(&vc->start_time); ofono_dbus_signal_property_changed(conn, path, OFONO_CDMA_VOICECALL_MANAGER_INTERFACE, "StartTime", DBUS_TYPE_STRING, ×tr); } /* TODO: Properly signal property changes here */ if (status == CDMA_CALL_STATUS_DISCONNECTED) { memset(&vc->phone_number, 0, sizeof(struct ofono_cdma_phone_number)); memset(&vc->waiting_number, 0, sizeof(struct ofono_cdma_phone_number)); } } static void voicecall_set_call_lineid(struct ofono_cdma_voicecall *vc) { DBusConnection *conn = ofono_dbus_get_connection(); const char *path = __ofono_atom_get_path(vc->atom); const char *lineid_str; /* For MO calls, LineID is the dialed phone number */ lineid_str = cdma_phone_number_to_string(&vc->phone_number); ofono_dbus_signal_property_changed(conn, path, OFONO_CDMA_VOICECALL_MANAGER_INTERFACE, "LineIdentification", DBUS_TYPE_STRING, &lineid_str); } static void manager_dial_callback(const struct ofono_error *error, void *data) { struct ofono_cdma_voicecall *vc = data; DBusMessage *reply; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { reply = __ofono_error_failed(vc->pending); __ofono_dbus_pending_reply(&vc->pending, reply); return; } voicecall_set_call_lineid(vc); vc->direction = CALL_DIRECTION_MOBILE_ORIGINATED; voicecall_set_call_status(vc, CDMA_CALL_STATUS_DIALING); reply = dbus_message_new_method_return(vc->pending); __ofono_dbus_pending_reply(&vc->pending, reply); } static DBusMessage *voicecall_manager_dial(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_cdma_voicecall *vc = data; const char *number; if (vc->pending) return __ofono_error_busy(msg); if (vc->status != CDMA_CALL_STATUS_DISCONNECTED) return __ofono_error_failed(msg); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, DBUS_TYPE_INVALID) == FALSE) return __ofono_error_invalid_args(msg); if (!valid_cdma_phone_number_format(number)) return __ofono_error_invalid_format(msg); if (vc->driver->dial == NULL) return __ofono_error_not_implemented(msg); vc->pending = dbus_message_ref(msg); string_to_cdma_phone_number(number, &vc->phone_number); vc->driver->dial(vc, &vc->phone_number, manager_dial_callback, vc); return NULL; } static DBusMessage *voicecall_manager_hangup(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_cdma_voicecall *vc = data; if (vc->pending) return __ofono_error_busy(msg); if (vc->driver->hangup == NULL) return __ofono_error_not_implemented(msg); if (vc->status == CDMA_CALL_STATUS_DISCONNECTED) return __ofono_error_failed(msg); vc->pending = dbus_message_ref(msg); vc->driver->hangup(vc, generic_callback, vc); return NULL; } static DBusMessage *voicecall_manager_answer(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_cdma_voicecall *vc = data; if (vc->pending) return __ofono_error_busy(msg); if (vc->driver->answer == NULL) return __ofono_error_not_implemented(msg); if (vc->status != CDMA_CALL_STATUS_INCOMING) return __ofono_error_failed(msg); vc->pending = dbus_message_ref(msg); vc->driver->answer(vc, generic_callback, vc); return NULL; } static DBusMessage *voicecall_manager_flash(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_cdma_voicecall *vc = data; const char *string; if (vc->pending) return __ofono_error_busy(msg); if (vc->driver->send_flash == NULL) return __ofono_error_not_implemented(msg); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &string, DBUS_TYPE_INVALID) == FALSE) return __ofono_error_invalid_args(msg); vc->pending = dbus_message_ref(msg); vc->driver->send_flash(vc, string, generic_callback, vc); return NULL; } static ofono_bool_t is_valid_tones(const char *tones) { int len; int i; if (tones == NULL) return FALSE; len = strlen(tones); if (len == 0) return FALSE; for (i = 0; i < len; i++) { if (g_ascii_isdigit(tones[i]) || tones[i] == '*' || tones[i] == '#') continue; else return FALSE; } return TRUE; } static DBusMessage *voicecall_manager_tone(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_cdma_voicecall *vc = data; const char *tones; if (vc->pending) return __ofono_error_busy(msg); if (vc->driver->send_tones == NULL) return __ofono_error_not_implemented(msg); if (vc->status != CDMA_CALL_STATUS_ACTIVE) return __ofono_error_failed(msg); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &tones, DBUS_TYPE_INVALID) == FALSE) return __ofono_error_invalid_args(msg); if (is_valid_tones(tones) == FALSE) return __ofono_error_invalid_args(msg); vc->pending = dbus_message_ref(msg); vc->driver->send_tones(vc, tones, generic_callback, vc); return NULL; } static const GDBusMethodTable manager_methods[] = { { GDBUS_METHOD("GetProperties", NULL, GDBUS_ARGS({ "properties", "a{sv}" }), voicecall_manager_get_properties) }, { GDBUS_ASYNC_METHOD("Dial", GDBUS_ARGS({ "number", "s" }), NULL, voicecall_manager_dial) }, { GDBUS_ASYNC_METHOD("Hangup", NULL, NULL, voicecall_manager_hangup) }, { GDBUS_ASYNC_METHOD("Answer", NULL, NULL, voicecall_manager_answer) }, { GDBUS_ASYNC_METHOD("SendFlash", GDBUS_ARGS({ "flash_string", "s" }), NULL, voicecall_manager_flash) }, { GDBUS_ASYNC_METHOD("SendTones", GDBUS_ARGS({ "tones", "s" }), NULL, voicecall_manager_tone) }, { } }; static const GDBusSignalTable manager_signals[] = { { GDBUS_SIGNAL("PropertyChanged", GDBUS_ARGS({ "name", "s" }, { "value", "v" })) }, { GDBUS_SIGNAL("DisconnectReason", GDBUS_ARGS({ "reason", "s" })) }, { } }; void ofono_cdma_voicecall_disconnected(struct ofono_cdma_voicecall *vc, enum ofono_disconnect_reason reason, const struct ofono_error *error) { DBG("Got disconnection event for reason: %d", reason); if (reason != OFONO_DISCONNECT_REASON_UNKNOWN) voicecall_emit_disconnect_reason(vc, reason); voicecall_set_call_status(vc, CDMA_CALL_STATUS_DISCONNECTED); } int ofono_cdma_voicecall_driver_register( const struct ofono_cdma_voicecall_driver *d) { DBG("driver: %p, name: %s", d, d->name); if (d->probe == NULL) return -EINVAL; g_drivers = g_slist_prepend(g_drivers, (void *)d); return 0; } void ofono_cdma_voicecall_driver_unregister( const struct ofono_cdma_voicecall_driver *d) { DBG("driver: %p, name: %s", d, d->name); g_drivers = g_slist_remove(g_drivers, (void *)d); } static void cdma_voicecall_unregister(struct ofono_atom *atom) { DBusConnection *conn = ofono_dbus_get_connection(); struct ofono_modem *modem = __ofono_atom_get_modem(atom); const char *path = __ofono_atom_get_path(atom); g_dbus_unregister_interface(conn, path, OFONO_CDMA_VOICECALL_MANAGER_INTERFACE); ofono_modem_remove_interface(modem, OFONO_CDMA_VOICECALL_MANAGER_INTERFACE); } static void voicecall_manager_remove(struct ofono_atom *atom) { struct ofono_cdma_voicecall *vc = __ofono_atom_get_data(atom); DBG("atom: %p", atom); if (vc == NULL) return; if (vc->driver && vc->driver->remove) vc->driver->remove(vc); g_free(vc); } struct ofono_cdma_voicecall *ofono_cdma_voicecall_create( struct ofono_modem *modem, unsigned int vendor, const char *driver, void *data) { struct ofono_cdma_voicecall *vc; GSList *l; if (driver == NULL) return NULL; vc = g_try_new0(struct ofono_cdma_voicecall, 1); if (vc == NULL) return NULL; vc->status = CDMA_CALL_STATUS_DISCONNECTED; vc->atom = __ofono_modem_add_atom(modem, OFONO_ATOM_TYPE_CDMA_VOICECALL_MANAGER, voicecall_manager_remove, vc); for (l = g_drivers; l; l = l->next) { const struct ofono_cdma_voicecall_driver *drv = l->data; if (g_strcmp0(drv->name, driver)) continue; if (drv->probe(vc, vendor, data) < 0) continue; vc->driver = drv; break; } return vc; } void ofono_cdma_voicecall_register(struct ofono_cdma_voicecall *vc) { DBusConnection *conn = ofono_dbus_get_connection(); struct ofono_modem *modem = __ofono_atom_get_modem(vc->atom); const char *path = __ofono_atom_get_path(vc->atom); if (!g_dbus_register_interface(conn, path, OFONO_CDMA_VOICECALL_MANAGER_INTERFACE, manager_methods, manager_signals, NULL, vc, NULL)) { ofono_error("Could not create %s interface", OFONO_CDMA_VOICECALL_MANAGER_INTERFACE); return; } ofono_modem_add_interface(modem, OFONO_CDMA_VOICECALL_MANAGER_INTERFACE); __ofono_atom_register(vc->atom, cdma_voicecall_unregister); } void ofono_cdma_voicecall_remove(struct ofono_cdma_voicecall *vc) { __ofono_atom_free(vc->atom); } void ofono_cdma_voicecall_set_data(struct ofono_cdma_voicecall *vc, void *data) { vc->driver_data = data; } void *ofono_cdma_voicecall_get_data(struct ofono_cdma_voicecall *vc) { return vc->driver_data; }