diff options
Diffstat (limited to 'audio/telephony-maemo6.c')
-rw-r--r-- | audio/telephony-maemo6.c | 1993 |
1 files changed, 1993 insertions, 0 deletions
diff --git a/audio/telephony-maemo6.c b/audio/telephony-maemo6.c new file mode 100644 index 0000000..0cef7dd --- /dev/null +++ b/audio/telephony-maemo6.c @@ -0,0 +1,1993 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdint.h> +#include <string.h> +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "log.h" +#include "telephony.h" +#include "error.h" + +/* SSC D-Bus definitions */ +#define SSC_DBUS_NAME "com.nokia.phone.SSC" +#define SSC_DBUS_IFACE "com.nokia.phone.SSC" +#define SSC_DBUS_PATH "/com/nokia/phone/SSC" + +/* libcsnet D-Bus definitions */ +#define CSD_CSNET_BUS_NAME "com.nokia.csd.CSNet" +#define CSD_CSNET_PATH "/com/nokia/csd/csnet" +#define CSD_CSNET_IFACE "com.nokia.csd.CSNet" +#define CSD_CSNET_REGISTRATION "com.nokia.csd.CSNet.NetworkRegistration" +#define CSD_CSNET_OPERATOR "com.nokia.csd.CSNet.NetworkOperator" +#define CSD_CSNET_SIGNAL "com.nokia.csd.CSNet.SignalStrength" + +enum net_registration_status { + NETWORK_REG_STATUS_HOME, + NETWORK_REG_STATUS_ROAMING, + NETWORK_REG_STATUS_OFFLINE, + NETWORK_REG_STATUS_SEARCHING, + NETWORK_REG_STATUS_NO_SIM, + NETWORK_REG_STATUS_POWEROFF, + NETWORK_REG_STATUS_POWERSAFE, + NETWORK_REG_STATUS_NO_COVERAGE, + NETWORK_REG_STATUS_REJECTED, + NETWORK_REG_STATUS_UNKOWN +}; + +/* CSD CALL plugin D-Bus definitions */ +#define CSD_CALL_BUS_NAME "com.nokia.csd.Call" +#define CSD_CALL_INTERFACE "com.nokia.csd.Call" +#define CSD_CALL_INSTANCE "com.nokia.csd.Call.Instance" +#define CSD_CALL_CONFERENCE "com.nokia.csd.Call.Conference" +#define CSD_CALL_PATH "/com/nokia/csd/call" +#define CSD_CALL_CONFERENCE_PATH "/com/nokia/csd/call/conference" + +/* Call status values as exported by the CSD CALL plugin */ +#define CSD_CALL_STATUS_IDLE 0 +#define CSD_CALL_STATUS_CREATE 1 +#define CSD_CALL_STATUS_COMING 2 +#define CSD_CALL_STATUS_PROCEEDING 3 +#define CSD_CALL_STATUS_MO_ALERTING 4 +#define CSD_CALL_STATUS_MT_ALERTING 5 +#define CSD_CALL_STATUS_WAITING 6 +#define CSD_CALL_STATUS_ANSWERED 7 +#define CSD_CALL_STATUS_ACTIVE 8 +#define CSD_CALL_STATUS_MO_RELEASE 9 +#define CSD_CALL_STATUS_MT_RELEASE 10 +#define CSD_CALL_STATUS_HOLD_INITIATED 11 +#define CSD_CALL_STATUS_HOLD 12 +#define CSD_CALL_STATUS_RETRIEVE_INITIATED 13 +#define CSD_CALL_STATUS_RECONNECT_PENDING 14 +#define CSD_CALL_STATUS_TERMINATED 15 +#define CSD_CALL_STATUS_SWAP_INITIATED 16 + +#define CALL_FLAG_NONE 0 +#define CALL_FLAG_PRESENTATION_ALLOWED 0x01 +#define CALL_FLAG_PRESENTATION_RESTRICTED 0x02 + +/* SIM Phonebook D-Bus definitions */ +#define CSD_SIMPB_BUS_NAME "com.nokia.csd.SIM" +#define CSD_SIMPB_INTERFACE "com.nokia.csd.SIM.Phonebook" +#define CSD_SIMPB_PATH "/com/nokia/csd/sim/phonebook" + +#define CSD_SIMPB_TYPE_ADN "ADN" +#define CSD_SIMPB_TYPE_FDN "FDN" +#define CSD_SIMPB_TYPE_SDN "SDN" +#define CSD_SIMPB_TYPE_VMBX "VMBX" +#define CSD_SIMPB_TYPE_MBDN "MBDN" +#define CSD_SIMPB_TYPE_EN "EN" +#define CSD_SIMPB_TYPE_MSISDN "MSISDN" + +struct csd_call { + char *object_path; + int status; + gboolean originating; + gboolean emergency; + gboolean on_hold; + gboolean conference; + char *number; + gboolean setup; +}; + +static struct { + char *operator_name; + uint8_t status; + int32_t signal_bars; +} net = { + .operator_name = NULL, + .status = NETWORK_REG_STATUS_UNKOWN, + /* Init as 0 meaning inactive mode. In modem power off state + * can be be -1, but we treat all values as 0s regardless + * inactive or power off. */ + .signal_bars = 0, +}; + +struct pending_req { + DBusPendingCall *call; + void *user_data; +}; + +static int get_property(const char *iface, const char *prop); + +static DBusConnection *connection = NULL; + +static GSList *calls = NULL; +static GSList *watches = NULL; +static GSList *pending = NULL; + +/* Reference count for determining the call indicator status */ +static GSList *active_calls = NULL; + +static char *msisdn = NULL; /* Subscriber number */ +static char *vmbx = NULL; /* Voice mailbox number */ + +/* HAL battery namespace key values */ +static int battchg_cur = -1; /* "battery.charge_level.current" */ +static int battchg_last = -1; /* "battery.charge_level.last_full" */ +static int battchg_design = -1; /* "battery.charge_level.design" */ + +static gboolean get_calls_active = FALSE; + +static gboolean events_enabled = FALSE; + +/* Supported set of call hold operations */ +static const char *chld_str = "0,1,1x,2,2x,3,4"; + +/* Timer for tracking call creation requests */ +static guint create_request_timer = 0; + +static struct indicator maemo_indicators[] = +{ + { "battchg", "0-5", 5, TRUE }, + /* signal strength in terms of bars */ + { "signal", "0-5", 0, TRUE }, + { "service", "0,1", 0, TRUE }, + { "call", "0,1", 0, TRUE }, + { "callsetup", "0-3", 0, TRUE }, + { "callheld", "0-2", 0, FALSE }, + { "roam", "0,1", 0, TRUE }, + { NULL } +}; + +static char *call_status_str[] = { + "IDLE", + "CREATE", + "COMING", + "PROCEEDING", + "MO_ALERTING", + "MT_ALERTING", + "WAITING", + "ANSWERED", + "ACTIVE", + "MO_RELEASE", + "MT_RELEASE", + "HOLD_INITIATED", + "HOLD", + "RETRIEVE_INITIATED", + "RECONNECT_PENDING", + "TERMINATED", + "SWAP_INITIATED", + "???" +}; + +static struct csd_call *find_call(const char *path) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (g_str_equal(call->object_path, path)) + return call; + } + + return NULL; +} + +static struct csd_call *find_non_held_call(void) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status == CSD_CALL_STATUS_IDLE) + continue; + + if (call->status != CSD_CALL_STATUS_HOLD) + return call; + } + + return NULL; +} + +static struct csd_call *find_non_idle_call(void) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status != CSD_CALL_STATUS_IDLE) + return call; + } + + return NULL; +} + +static struct csd_call *find_call_with_status(int status) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status == status) + return call; + } + + return NULL; +} + +static int release_conference(void) +{ + DBusMessage *msg; + + DBG("telephony-maemo6: releasing conference call"); + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + CSD_CALL_CONFERENCE_PATH, + CSD_CALL_INSTANCE, + "Release"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int release_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + call->object_path, + CSD_CALL_INSTANCE, + "Release"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int answer_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + call->object_path, + CSD_CALL_INSTANCE, + "Answer"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int split_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, + call->object_path, + CSD_CALL_INSTANCE, + "Split"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int unhold_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Unhold"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int hold_call(struct csd_call *call) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Hold"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int swap_calls(void) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Swap"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int create_conference(void) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Conference"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int call_transfer(void) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, + "Transfer"); + if (!msg) { + error("Unable to allocate new D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, msg); + + return 0; +} + +static int number_type(const char *number) +{ + if (number == NULL) + return NUMBER_TYPE_TELEPHONY; + + if (number[0] == '+' || strncmp(number, "00", 2) == 0) + return NUMBER_TYPE_INTERNATIONAL; + + return NUMBER_TYPE_TELEPHONY; +} + +void telephony_device_connected(void *telephony_device) +{ + struct csd_call *coming; + + DBG("telephony-maemo6: device %p connected", telephony_device); + + coming = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING); + if (coming) { + if (find_call_with_status(CSD_CALL_STATUS_ACTIVE)) + telephony_call_waiting_ind(coming->number, + number_type(coming->number)); + else + telephony_incoming_call_ind(coming->number, + number_type(coming->number)); + } +} + +static void pending_req_finalize(struct pending_req *req) +{ + if (!dbus_pending_call_get_completed(req->call)) + dbus_pending_call_cancel(req->call); + + dbus_pending_call_unref(req->call); + g_free(req); +} + +static void remove_pending_by_data(gpointer data, gpointer user_data) +{ + struct pending_req *req = data; + + if (req->user_data == user_data) { + pending = g_slist_remove(pending, req); + pending_req_finalize(req); + } +} + +void telephony_device_disconnected(void *telephony_device) +{ + DBG("telephony-maemo6: device %p disconnected", telephony_device); + events_enabled = FALSE; + + g_slist_foreach(pending, remove_pending_by_data, telephony_device); +} + +void telephony_event_reporting_req(void *telephony_device, int ind) +{ + events_enabled = ind == 1 ? TRUE : FALSE; + + telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_response_and_hold_req(void *telephony_device, int rh) +{ + telephony_response_and_hold_rsp(telephony_device, + CME_ERROR_NOT_SUPPORTED); +} + +void telephony_terminate_call_req(void *telephony_device) +{ + struct csd_call *call; + struct csd_call *alerting; + int err; + + call = find_call_with_status(CSD_CALL_STATUS_ACTIVE); + if (!call) + call = find_non_idle_call(); + + if (!call) { + error("No active call"); + telephony_terminate_call_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); + return; + } + + alerting = find_call_with_status(CSD_CALL_STATUS_MO_ALERTING); + if (call->on_hold && alerting) + err = release_call(alerting); + else if (call->conference) + err = release_conference(); + else + err = release_call(call); + + if (err < 0) + telephony_terminate_call_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_answer_call_req(void *telephony_device) +{ + struct csd_call *call; + + call = find_call_with_status(CSD_CALL_STATUS_COMING); + if (!call) + call = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING); + + if (!call) + call = find_call_with_status(CSD_CALL_STATUS_PROCEEDING); + + if (!call) + call = find_call_with_status(CSD_CALL_STATUS_WAITING); + + if (!call) { + telephony_answer_call_rsp(telephony_device, + CME_ERROR_NOT_ALLOWED); + return; + } + + if (answer_call(call) < 0) + telephony_answer_call_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE); +} + +static int send_method_call(const char *dest, const char *path, + const char *interface, const char *method, + DBusPendingCallNotifyFunction cb, + void *user_data, int type, ...) +{ + DBusMessage *msg; + DBusPendingCall *call; + va_list args; + struct pending_req *req; + + msg = dbus_message_new_method_call(dest, path, interface, method); + if (!msg) { + error("Unable to allocate new D-Bus %s message", method); + return -ENOMEM; + } + + va_start(args, type); + + if (!dbus_message_append_args_valist(msg, type, args)) { + dbus_message_unref(msg); + va_end(args); + return -EIO; + } + + va_end(args); + + if (!cb) { + g_dbus_send_message(connection, msg); + return 0; + } + + if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) { + error("Sending %s failed", method); + dbus_message_unref(msg); + return -EIO; + } + + dbus_pending_call_set_notify(call, cb, user_data, NULL); + + req = g_new0(struct pending_req, 1); + req->call = call; + req->user_data = user_data; + + pending = g_slist_prepend(pending, req); + dbus_message_unref(msg); + + return 0; +} + +static struct pending_req *find_request(const DBusPendingCall *call) +{ + GSList *l; + + for (l = pending; l; l = l->next) { + struct pending_req *req = l->data; + + if (req->call == call) + return req; + } + + return NULL; +} + +static void remove_pending(DBusPendingCall *call) +{ + struct pending_req *req = find_request(call); + + pending = g_slist_remove(pending, req); + pending_req_finalize(req); +} + +static void last_number_call_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + void *telephony_device = user_data; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("csd replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + telephony_dial_number_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + } else + telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE); + + dbus_message_unref(reply); + remove_pending(call); +} + +void telephony_last_dialed_number_req(void *telephony_device) +{ + int ret; + + DBG("telephony-maemo6: last dialed number request"); + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "CreateFromLast", + last_number_call_reply, telephony_device, + DBUS_TYPE_INVALID); + if (ret < 0) + telephony_dial_number_rsp(telephony_device, + CME_ERROR_AG_FAILURE); +} + +static const char *memory_dial_lookup(int location) +{ + if (location == 1) + return vmbx; + else + return NULL; +} + +void telephony_dial_number_req(void *telephony_device, const char *number) +{ + int ret; + + DBG("telephony-maemo6: dial request to %s", number); + + if (strncmp(number, "*31#", 4) == 0) + number += 4; + else if (strncmp(number, "#31#", 4) == 0) + number += 4; + else if (number[0] == '>') { + const char *location = &number[1]; + + number = memory_dial_lookup(strtol(&number[1], NULL, 0)); + if (!number) { + error("No number at memory location %s", location); + telephony_dial_number_rsp(telephony_device, + CME_ERROR_INVALID_INDEX); + return; + } + } + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "Create", + NULL, NULL, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID); + if (ret < 0) { + telephony_dial_number_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_transmit_dtmf_req(void *telephony_device, char tone) +{ + int ret; + char buf[2] = { tone, '\0' }, *buf_ptr = buf; + + DBG("telephony-maemo6: transmit dtmf: %s", buf); + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "SendDTMF", + NULL, NULL, + DBUS_TYPE_STRING, &buf_ptr, + DBUS_TYPE_INVALID); + if (ret < 0) { + telephony_transmit_dtmf_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + return; + } + + telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_subscriber_number_req(void *telephony_device) +{ + DBG("telephony-maemo6: subscriber number request"); + if (msisdn) + telephony_subscriber_number_ind(msisdn, + number_type(msisdn), + SUBSCRIBER_SERVICE_VOICE); + telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE); +} + +static int csd_status_to_hfp(struct csd_call *call) +{ + switch (call->status) { + case CSD_CALL_STATUS_IDLE: + case CSD_CALL_STATUS_MO_RELEASE: + case CSD_CALL_STATUS_MT_RELEASE: + case CSD_CALL_STATUS_TERMINATED: + return -1; + case CSD_CALL_STATUS_CREATE: + return CALL_STATUS_DIALING; + case CSD_CALL_STATUS_WAITING: + return CALL_STATUS_WAITING; + case CSD_CALL_STATUS_PROCEEDING: + /* PROCEEDING can happen in outgoing/incoming */ + if (call->originating) + return CALL_STATUS_DIALING; + else + return CALL_STATUS_INCOMING; + case CSD_CALL_STATUS_COMING: + return CALL_STATUS_INCOMING; + case CSD_CALL_STATUS_MO_ALERTING: + return CALL_STATUS_ALERTING; + case CSD_CALL_STATUS_MT_ALERTING: + return CALL_STATUS_INCOMING; + case CSD_CALL_STATUS_ANSWERED: + case CSD_CALL_STATUS_ACTIVE: + case CSD_CALL_STATUS_RECONNECT_PENDING: + case CSD_CALL_STATUS_SWAP_INITIATED: + case CSD_CALL_STATUS_HOLD_INITIATED: + return CALL_STATUS_ACTIVE; + case CSD_CALL_STATUS_RETRIEVE_INITIATED: + case CSD_CALL_STATUS_HOLD: + return CALL_STATUS_HELD; + default: + return -1; + } +} + +void telephony_list_current_calls_req(void *telephony_device) +{ + GSList *l; + int i; + + DBG("telephony-maemo6: list current calls request"); + + for (l = calls, i = 1; l != NULL; l = l->next, i++) { + struct csd_call *call = l->data; + int status, direction, multiparty; + + status = csd_status_to_hfp(call); + if (status < 0) + continue; + + direction = call->originating ? + CALL_DIR_OUTGOING : CALL_DIR_INCOMING; + + multiparty = call->conference ? + CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO; + + telephony_list_current_call_ind(i, direction, status, + CALL_MODE_VOICE, multiparty, + call->number, + number_type(call->number)); + } + + telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_operator_selection_req(void *telephony_device) +{ + telephony_operator_selection_ind(OPERATOR_MODE_AUTO, + net.operator_name ? net.operator_name : ""); + telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE); +} + +static void foreach_call_with_status(int status, + int (*func)(struct csd_call *call)) +{ + GSList *l; + + for (l = calls; l != NULL; l = l->next) { + struct csd_call *call = l->data; + + if (call->status == status) + func(call); + } +} + +void telephony_call_hold_req(void *telephony_device, const char *cmd) +{ + const char *idx; + struct csd_call *call; + int err = 0; + + DBG("telephony-maemo6: got call hold request %s", cmd); + + if (strlen(cmd) > 1) + idx = &cmd[1]; + else + idx = NULL; + + if (idx) + call = g_slist_nth_data(calls, strtol(idx, NULL, 0) - 1); + else + call = NULL; + + switch (cmd[0]) { + case '0': + if (find_call_with_status(CSD_CALL_STATUS_WAITING)) + foreach_call_with_status(CSD_CALL_STATUS_WAITING, + release_call); + else + foreach_call_with_status(CSD_CALL_STATUS_HOLD, + release_call); + break; + case '1': + if (idx) { + if (call) + err = release_call(call); + break; + } + foreach_call_with_status(CSD_CALL_STATUS_ACTIVE, release_call); + call = find_call_with_status(CSD_CALL_STATUS_WAITING); + if (call) + err = answer_call(call); + break; + case '2': + if (idx) { + if (call) + err = split_call(call); + } else { + struct csd_call *held, *wait; + + call = find_call_with_status(CSD_CALL_STATUS_ACTIVE); + held = find_call_with_status(CSD_CALL_STATUS_HOLD); + wait = find_call_with_status(CSD_CALL_STATUS_WAITING); + + if (wait) + err = answer_call(wait); + else if (call && held) + err = swap_calls(); + else { + if (call) + err = hold_call(call); + if (held) + err = unhold_call(held); + } + } + break; + case '3': + if (find_call_with_status(CSD_CALL_STATUS_HOLD) || + find_call_with_status(CSD_CALL_STATUS_WAITING)) + err = create_conference(); + break; + case '4': + err = call_transfer(); + break; + default: + DBG("Unknown call hold request"); + break; + } + + if (err) + telephony_call_hold_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_nr_and_ec_req(void *telephony_device, gboolean enable) +{ + DBG("telephony-maemo6: got %s NR and EC request", + enable ? "enable" : "disable"); + telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_key_press_req(void *telephony_device, const char *keys) +{ + struct csd_call *active, *waiting; + int err; + + DBG("telephony-maemo6: got key press request for %s", keys); + + waiting = find_call_with_status(CSD_CALL_STATUS_COMING); + if (!waiting) + waiting = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING); + if (!waiting) + waiting = find_call_with_status(CSD_CALL_STATUS_PROCEEDING); + + active = find_call_with_status(CSD_CALL_STATUS_ACTIVE); + + if (waiting) + err = answer_call(waiting); + else if (active) + err = release_call(active); + else + err = 0; + + if (err < 0) + telephony_key_press_rsp(telephony_device, + CME_ERROR_AG_FAILURE); + else + telephony_key_press_rsp(telephony_device, CME_ERROR_NONE); +} + +void telephony_voice_dial_req(void *telephony_device, gboolean enable) +{ + DBG("telephony-maemo6: got %s voice dial request", + enable ? "enable" : "disable"); + + telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED); +} + +static void handle_incoming_call(DBusMessage *msg) +{ + const char *number, *call_path; + struct csd_call *call; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &call_path, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in Call.Coming() signal"); + return; + } + + call = find_call(call_path); + if (!call) { + error("Didn't find any matching call object for %s", + call_path); + return; + } + + DBG("Incoming call to %s from number %s", call_path, number); + + g_free(call->number); + call->number = g_strdup(number); + + if (find_call_with_status(CSD_CALL_STATUS_ACTIVE) || + find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_call_waiting_ind(call->number, + number_type(call->number)); + else + telephony_incoming_call_ind(call->number, + number_type(call->number)); + + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_INCOMING); +} + +static void handle_outgoing_call(DBusMessage *msg) +{ + const char *number, *call_path; + struct csd_call *call; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &call_path, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in Call.Created() signal"); + return; + } + + call = find_call(call_path); + if (!call) { + error("Didn't find any matching call object for %s", + call_path); + return; + } + + DBG("Outgoing call from %s to number %s", call_path, number); + + g_free(call->number); + call->number = g_strdup(number); + + if (create_request_timer) { + g_source_remove(create_request_timer); + create_request_timer = 0; + } +} + +static gboolean create_timeout(gpointer user_data) +{ + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_INACTIVE); + create_request_timer = 0; + return FALSE; +} + +static void handle_create_requested(DBusMessage *msg) +{ + DBG("Call.CreateRequested()"); + + if (create_request_timer) + g_source_remove(create_request_timer); + + create_request_timer = g_timeout_add_seconds(5, create_timeout, NULL); + + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_OUTGOING); +} + +static void call_set_status(struct csd_call *call, dbus_uint32_t status) +{ + dbus_uint32_t prev_status; + int callheld = telephony_get_indicator(maemo_indicators, "callheld"); + + prev_status = call->status; + DBG("Call %s changed from %s to %s", call->object_path, + call_status_str[prev_status], call_status_str[status]); + + if (prev_status == status) { + DBG("Ignoring CSD Call state change to existing state"); + return; + } + + call->status = (int) status; + + switch (status) { + case CSD_CALL_STATUS_IDLE: + if (call->setup) { + telephony_update_indicator(maemo_indicators, + "callsetup", + EV_CALLSETUP_INACTIVE); + if (!call->originating) + telephony_calling_stopped_ind(); + } + + g_free(call->number); + call->number = NULL; + call->originating = FALSE; + call->emergency = FALSE; + call->on_hold = FALSE; + call->conference = FALSE; + call->setup = FALSE; + break; + case CSD_CALL_STATUS_CREATE: + call->originating = TRUE; + call->setup = TRUE; + break; + case CSD_CALL_STATUS_COMING: + call->originating = FALSE; + call->setup = TRUE; + break; + case CSD_CALL_STATUS_PROCEEDING: + break; + case CSD_CALL_STATUS_MO_ALERTING: + telephony_update_indicator(maemo_indicators, "callsetup", + EV_CALLSETUP_ALERTING); + break; + case CSD_CALL_STATUS_MT_ALERTING: + /* Some headsets expect incoming call notification before they + * can send ATA command. When call changed status from waiting + * to alerting we need to send missing notification. Otherwise + * headsets like Nokia BH-108 or BackBeat 903 are unable to + * answer incoming call that was previously waiting. */ + if (prev_status == CSD_CALL_STATUS_WAITING) + telephony_incoming_call_ind(call->number, + number_type(call->number)); + break; + case CSD_CALL_STATUS_WAITING: + break; + case CSD_CALL_STATUS_ANSWERED: + break; + case CSD_CALL_STATUS_ACTIVE: + if (call->on_hold) { + call->on_hold = FALSE; + if (find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + else + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_NONE); + } else { + if (!g_slist_find(active_calls, call)) + active_calls = g_slist_prepend(active_calls, call); + if (g_slist_length(active_calls) == 1) + telephony_update_indicator(maemo_indicators, + "call", + EV_CALL_ACTIVE); + /* Upgrade callheld status if necessary */ + if (callheld == EV_CALLHELD_ON_HOLD) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + telephony_update_indicator(maemo_indicators, + "callsetup", + EV_CALLSETUP_INACTIVE); + if (!call->originating) + telephony_calling_stopped_ind(); + call->setup = FALSE; + } + break; + case CSD_CALL_STATUS_MO_RELEASE: + case CSD_CALL_STATUS_MT_RELEASE: + active_calls = g_slist_remove(active_calls, call); + if (g_slist_length(active_calls) == 0) + telephony_update_indicator(maemo_indicators, "call", + EV_CALL_INACTIVE); + break; + case CSD_CALL_STATUS_HOLD_INITIATED: + break; + case CSD_CALL_STATUS_HOLD: + call->on_hold = TRUE; + if (find_non_held_call()) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_MULTIPLE); + else + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_ON_HOLD); + break; + case CSD_CALL_STATUS_RETRIEVE_INITIATED: + break; + case CSD_CALL_STATUS_RECONNECT_PENDING: + break; + case CSD_CALL_STATUS_TERMINATED: + if (call->on_hold && + !find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_NONE); + else if (callheld == EV_CALLHELD_MULTIPLE && + find_call_with_status(CSD_CALL_STATUS_HOLD)) + telephony_update_indicator(maemo_indicators, + "callheld", + EV_CALLHELD_ON_HOLD); + break; + case CSD_CALL_STATUS_SWAP_INITIATED: + break; + default: + error("Unknown call status %u", status); + break; + } +} + +static void handle_call_status(DBusMessage *msg, const char *call_path) +{ + struct csd_call *call; + dbus_uint32_t status, cause_type, cause; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &status, + DBUS_TYPE_UINT32, &cause_type, + DBUS_TYPE_UINT32, &cause, + DBUS_TYPE_INVALID)) { + error("Unexpected paramters in Instance.CallStatus() signal"); + return; + } + + call = find_call(call_path); + if (!call) { + error("Didn't find any matching call object for %s", + call_path); + return; + } + + if (status > 16) { + error("Invalid call status %u", status); + return; + } + + call_set_status(call, status); +} + +static void handle_conference(DBusMessage *msg, gboolean joined) +{ + const char *path; + struct csd_call *call; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in Conference.%s", + dbus_message_get_member(msg)); + return; + } + + call = find_call(path); + if (!call) { + error("Conference signal for unknown call %s", path); + return; + } + + DBG("Call %s %s the conference", path, joined ? "joined" : "left"); + + call->conference = joined; +} + +static uint8_t str2status(const char *state) +{ + if (g_strcmp0(state, "Home") == 0) + return NETWORK_REG_STATUS_HOME; + else if (g_strcmp0(state, "Roaming") == 0) + return NETWORK_REG_STATUS_ROAMING; + else if (g_strcmp0(state, "Offline") == 0) + return NETWORK_REG_STATUS_OFFLINE; + else if (g_strcmp0(state, "Searching") == 0) + return NETWORK_REG_STATUS_SEARCHING; + else if (g_strcmp0(state, "NoSim") == 0) + return NETWORK_REG_STATUS_NO_SIM; + else if (g_strcmp0(state, "Poweroff") == 0) + return NETWORK_REG_STATUS_POWEROFF; + else if (g_strcmp0(state, "Powersafe") == 0) + return NETWORK_REG_STATUS_POWERSAFE; + else if (g_strcmp0(state, "NoCoverage") == 0) + return NETWORK_REG_STATUS_NO_COVERAGE; + else if (g_strcmp0(state, "Reject") == 0) + return NETWORK_REG_STATUS_REJECTED; + else + return NETWORK_REG_STATUS_UNKOWN; +} + +static void update_registration_status(const char *status) +{ + uint8_t new_status; + + new_status = str2status(status); + + if (net.status == new_status) + return; + + switch (new_status) { + case NETWORK_REG_STATUS_HOME: + telephony_update_indicator(maemo_indicators, "roam", + EV_ROAM_INACTIVE); + if (net.status > NETWORK_REG_STATUS_ROAMING) + telephony_update_indicator(maemo_indicators, + "service", + EV_SERVICE_PRESENT); + break; + case NETWORK_REG_STATUS_ROAMING: + telephony_update_indicator(maemo_indicators, "roam", + EV_ROAM_ACTIVE); + if (net.status > NETWORK_REG_STATUS_ROAMING) + telephony_update_indicator(maemo_indicators, + "service", + EV_SERVICE_PRESENT); + break; + case NETWORK_REG_STATUS_OFFLINE: + case NETWORK_REG_STATUS_SEARCHING: + case NETWORK_REG_STATUS_NO_SIM: + case NETWORK_REG_STATUS_POWEROFF: + case NETWORK_REG_STATUS_POWERSAFE: + case NETWORK_REG_STATUS_NO_COVERAGE: + case NETWORK_REG_STATUS_REJECTED: + case NETWORK_REG_STATUS_UNKOWN: + if (net.status < NETWORK_REG_STATUS_OFFLINE) + telephony_update_indicator(maemo_indicators, + "service", + EV_SERVICE_NONE); + break; + } + + net.status = new_status; + + DBG("telephony-maemo6: registration status changed: %s", status); +} + +static void handle_registration_changed(DBusMessage *msg) +{ + const char *status; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &status, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in RegistrationChanged"); + return; + } + + update_registration_status(status); +} + +static void update_signal_strength(int32_t signal_bars) +{ + if (signal_bars < 0) { + DBG("signal strength smaller than expected: %d < 0", + signal_bars); + signal_bars = 0; + } else if (signal_bars > 5) { + DBG("signal strength greater than expected: %d > 5", + signal_bars); + signal_bars = 5; + } + + if (net.signal_bars == signal_bars) + return; + + telephony_update_indicator(maemo_indicators, "signal", signal_bars); + + net.signal_bars = signal_bars; + DBG("telephony-maemo6: signal strength updated: %d/5", signal_bars); +} + +static void handle_signal_bars_changed(DBusMessage *msg) +{ + int32_t signal_bars; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_INT32, &signal_bars, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in SignalBarsChanged"); + return; + } + + update_signal_strength(signal_bars); +} + +static gboolean iter_get_basic_args(DBusMessageIter *iter, + int first_arg_type, ...) +{ + int type; + va_list ap; + + va_start(ap, first_arg_type); + + for (type = first_arg_type; type != DBUS_TYPE_INVALID; + type = va_arg(ap, int)) { + void *value = va_arg(ap, void *); + int real_type = dbus_message_iter_get_arg_type(iter); + + if (real_type != type) { + error("iter_get_basic_args: expected %c but got %c", + (char) type, (char) real_type); + break; + } + + dbus_message_iter_get_basic(iter, value); + dbus_message_iter_next(iter); + } + + va_end(ap); + + return type == DBUS_TYPE_INVALID ? TRUE : FALSE; +} + +static void hal_battery_level_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + dbus_int32_t level; + int *value = user_data; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("hald replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + if (!dbus_message_get_args(reply, NULL, + DBUS_TYPE_INT32, &level, + DBUS_TYPE_INVALID)) { + error("Unexpected args in hald reply"); + goto done; + } + + *value = (int) level; + + if (value == &battchg_last) + DBG("telephony-maemo6: battery.charge_level.last_full is %d", + *value); + else if (value == &battchg_design) + DBG("telephony-maemo6: battery.charge_level.design is %d", + *value); + else + DBG("telephony-maemo6: battery.charge_level.current is %d", + *value); + + if ((battchg_design > 0 || battchg_last > 0) && battchg_cur >= 0) { + int new, max; + + if (battchg_last > 0) + max = battchg_last; + else + max = battchg_design; + + new = battchg_cur * 5 / max; + + telephony_update_indicator(maemo_indicators, "battchg", new); + } + +done: + dbus_message_unref(reply); + remove_pending(call); +} + +static void hal_get_integer(const char *path, const char *key, void *user_data) +{ + send_method_call("org.freedesktop.Hal", path, + "org.freedesktop.Hal.Device", + "GetPropertyInteger", + hal_battery_level_reply, user_data, + DBUS_TYPE_STRING, &key, + DBUS_TYPE_INVALID); +} + +static void handle_hal_property_modified(DBusMessage *msg) +{ + DBusMessageIter iter, array; + dbus_int32_t num_changes; + const char *path; + + path = dbus_message_get_path(msg); + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) { + error("Unexpected signature in hal PropertyModified signal"); + return; + } + + dbus_message_iter_get_basic(&iter, &num_changes); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in hal PropertyModified signal"); + return; + } + + dbus_message_iter_recurse(&iter, &array); + + while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) { + DBusMessageIter prop; + const char *name; + dbus_bool_t added, removed; + + dbus_message_iter_recurse(&array, &prop); + + if (!iter_get_basic_args(&prop, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_BOOLEAN, &added, + DBUS_TYPE_BOOLEAN, &removed, + DBUS_TYPE_INVALID)) { + error("Invalid hal PropertyModified parameters"); + break; + } + + if (g_str_equal(name, "battery.charge_level.last_full")) + hal_get_integer(path, name, &battchg_last); + else if (g_str_equal(name, "battery.charge_level.current")) + hal_get_integer(path, name, &battchg_cur); + else if (g_str_equal(name, "battery.charge_level.design")) + hal_get_integer(path, name, &battchg_design); + + dbus_message_iter_next(&array); + } +} + +static void csd_call_free(struct csd_call *call) +{ + if (!call) + return; + + g_free(call->object_path); + g_free(call->number); + + g_free(call); +} + +static void parse_call_list(DBusMessageIter *iter) +{ + do { + DBusMessageIter call_iter; + struct csd_call *call; + const char *object_path, *number; + dbus_uint32_t status; + dbus_bool_t originating, terminating, emerg, on_hold, conf; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT) { + error("Unexpected signature in GetCallInfoAll reply"); + break; + } + + dbus_message_iter_recurse(iter, &call_iter); + + if (!iter_get_basic_args(&call_iter, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_UINT32, &status, + DBUS_TYPE_BOOLEAN, &originating, + DBUS_TYPE_BOOLEAN, &terminating, + DBUS_TYPE_BOOLEAN, &emerg, + DBUS_TYPE_BOOLEAN, &on_hold, + DBUS_TYPE_BOOLEAN, &conf, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) { + error("Parsing call D-Bus parameters failed"); + break; + } + + call = find_call(object_path); + if (!call) { + call = g_new0(struct csd_call, 1); + call->object_path = g_strdup(object_path); + calls = g_slist_append(calls, call); + DBG("telephony-maemo6: new csd call instance at %s", + object_path); + } + + if (status == CSD_CALL_STATUS_IDLE) + continue; + + /* CSD gives incorrect call_hold property sometimes */ + if ((call->status != CSD_CALL_STATUS_HOLD && on_hold) || + (call->status == CSD_CALL_STATUS_HOLD && + !on_hold)) { + error("Conflicting call status and on_hold property!"); + on_hold = call->status == CSD_CALL_STATUS_HOLD; + } + + call->originating = originating; + call->on_hold = on_hold; + call->conference = conf; + g_free(call->number); + call->number = g_strdup(number); + + /* Update indicators */ + call_set_status(call, status); + + } while (dbus_message_iter_next(iter)); +} + +static void update_operator_name(const char *name) +{ + if (name == NULL) + return; + + g_free(net.operator_name); + net.operator_name = g_strndup(name, 16); + DBG("telephony-maemo6: operator name updated: %s", name); +} + +static void get_property_reply(DBusPendingCall *call, void *user_data) +{ + char *prop = user_data; + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, sub; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("csd replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + error("Unexpected signature in Get return"); + goto done; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (g_strcmp0(prop, "RegistrationStatus") == 0) { + const char *status; + + dbus_message_iter_get_basic(&sub, &status); + update_registration_status(status); + + get_property(CSD_CSNET_OPERATOR, "OperatorName"); + get_property(CSD_CSNET_SIGNAL, "SignalBars"); + } else if (g_strcmp0(prop, "OperatorName") == 0) { + const char *name; + + dbus_message_iter_get_basic(&sub, &name); + update_operator_name(name); + } else if (g_strcmp0(prop, "SignalBars") == 0) { + int32_t signal_bars; + + dbus_message_iter_get_basic(&sub, &signal_bars); + update_signal_strength(signal_bars); + } + +done: + g_free(prop); + dbus_message_unref(reply); + remove_pending(call); +} + +static int get_property(const char *iface, const char *prop) +{ + return send_method_call(CSD_CSNET_BUS_NAME, CSD_CSNET_PATH, + DBUS_INTERFACE_PROPERTIES, "Get", + get_property_reply, g_strdup(prop), + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); +} + +static void handle_operator_name_changed(DBusMessage *msg) +{ + const char *name; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + error("Unexpected parameters in OperatorNameChanged"); + return; + } + + update_operator_name(name); +} + +static void call_info_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, sub;; + + get_calls_active = FALSE; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("csd replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in GetCallInfoAll return"); + goto done; + } + + dbus_message_iter_recurse(&iter, &sub); + + parse_call_list(&sub); + + get_property(CSD_CSNET_REGISTRATION, "RegistrationStatus"); + +done: + dbus_message_unref(reply); + remove_pending(call); +} + + +static void phonebook_read_reply(DBusPendingCall *call, void *user_data) +{ + DBusError derr; + DBusMessage *reply; + const char *name, *number, *secondname, *additionalnumber, *email; + int index; + char **number_type = user_data; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&derr); + if (dbus_set_error_from_message(&derr, reply)) { + error("%s.ReadFirst replied with an error: %s, %s", + CSD_SIMPB_INTERFACE, derr.name, derr.message); + dbus_error_free(&derr); + if (number_type == &vmbx) + vmbx = g_strdup(getenv("VMBX_NUMBER")); + goto done; + } + + dbus_error_init(&derr); + if (dbus_message_get_args(reply, NULL, + DBUS_TYPE_INT32, &index, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_STRING, &secondname, + DBUS_TYPE_STRING, &additionalnumber, + DBUS_TYPE_STRING, &email, + DBUS_TYPE_INVALID) == FALSE) { + error("Unable to parse %s.ReadFirst arguments: %s, %s", + CSD_SIMPB_INTERFACE, derr.name, derr.message); + dbus_error_free(&derr); + goto done; + } + + if (number_type == &msisdn) { + g_free(msisdn); + msisdn = g_strdup(number); + DBG("Got MSISDN %s (%s)", number, name); + } else { + g_free(vmbx); + vmbx = g_strdup(number); + DBG("Got voice mailbox number %s (%s)", number, name); + } + +done: + dbus_message_unref(reply); + remove_pending(call); +} + +static void csd_init(void) +{ + const char *pb_type; + int ret; + + ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH, + CSD_CALL_INTERFACE, "GetCallInfoAll", + call_info_reply, NULL, DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to sent GetCallInfoAll method call"); + return; + } + + get_calls_active = TRUE; + + pb_type = CSD_SIMPB_TYPE_MSISDN; + + ret = send_method_call(CSD_SIMPB_BUS_NAME, CSD_SIMPB_PATH, + CSD_SIMPB_INTERFACE, "ReadFirst", + phonebook_read_reply, &msisdn, + DBUS_TYPE_STRING, &pb_type, + DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to send " CSD_SIMPB_INTERFACE ".read()"); + return; + } + + /* Voicemail should be in MBDN index 0 */ + pb_type = CSD_SIMPB_TYPE_MBDN; + + ret = send_method_call(CSD_SIMPB_BUS_NAME, CSD_SIMPB_PATH, + CSD_SIMPB_INTERFACE, "ReadFirst", + phonebook_read_reply, &vmbx, + DBUS_TYPE_STRING, &pb_type, + DBUS_TYPE_INVALID); + if (ret < 0) { + error("Unable to send " CSD_SIMPB_INTERFACE ".read()"); + return; + } +} + +static void handle_modem_state(DBusMessage *msg) +{ + const char *state; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &state, + DBUS_TYPE_INVALID)) { + error("Unexpected modem state parameters"); + return; + } + + DBG("SSC modem state: %s", state); + + if (calls != NULL || get_calls_active) + return; + + if (g_str_equal(state, "cmt_ready") || g_str_equal(state, "online")) + csd_init(); +} + +static void modem_state_reply(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError err; + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("get_modem_state: %s, %s", err.name, err.message); + dbus_error_free(&err); + } else + handle_modem_state(reply); + + dbus_message_unref(reply); + remove_pending(call); +} + +static gboolean signal_filter(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *path = dbus_message_get_path(msg); + + if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Coming")) + handle_incoming_call(msg); + else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Created")) + handle_outgoing_call(msg); + else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, + "CreateRequested")) + handle_create_requested(msg); + else if (dbus_message_is_signal(msg, CSD_CALL_INSTANCE, "CallStatus")) + handle_call_status(msg, path); + else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Joined")) + handle_conference(msg, TRUE); + else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Left")) + handle_conference(msg, FALSE); + else if (dbus_message_is_signal(msg, CSD_CSNET_REGISTRATION, + "RegistrationChanged")) + handle_registration_changed(msg); + else if (dbus_message_is_signal(msg, CSD_CSNET_OPERATOR, + "OperatorNameChanged")) + handle_operator_name_changed(msg); + else if (dbus_message_is_signal(msg, CSD_CSNET_SIGNAL, + "SignalBarsChanged")) + handle_signal_bars_changed(msg); + else if (dbus_message_is_signal(msg, "org.freedesktop.Hal.Device", + "PropertyModified")) + handle_hal_property_modified(msg); + else if (dbus_message_is_signal(msg, SSC_DBUS_IFACE, + "modem_state_changed_ind")) + handle_modem_state(msg); + + return TRUE; +} + +static void add_watch(const char *sender, const char *path, + const char *interface, const char *member) +{ + guint watch; + + watch = g_dbus_add_signal_watch(connection, sender, path, interface, + member, signal_filter, NULL, NULL); + + watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch)); +} + +static void hal_find_device_reply(DBusPendingCall *call, void *user_data) +{ + DBusError err; + DBusMessage *reply; + DBusMessageIter iter, sub; + const char *path; + int type; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("hald replied with an error: %s, %s", + err.name, err.message); + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in FindDeviceByCapability return"); + goto done; + } + + dbus_message_iter_recurse(&iter, &sub); + + type = dbus_message_iter_get_arg_type(&sub); + + if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) { + error("No hal device with battery capability found"); + goto done; + } + + dbus_message_iter_get_basic(&sub, &path); + + DBG("telephony-maemo6: found battery device at %s", path); + + add_watch(NULL, path, "org.freedesktop.Hal.Device", + "PropertyModified"); + + hal_get_integer(path, "battery.charge_level.last_full", &battchg_last); + hal_get_integer(path, "battery.charge_level.current", &battchg_cur); + hal_get_integer(path, "battery.charge_level.design", &battchg_design); + +done: + dbus_message_unref(reply); + remove_pending(call); +} + +int telephony_init(void) +{ + const char *battery_cap = "battery"; + uint32_t features = AG_FEATURE_EC_ANDOR_NR | + AG_FEATURE_INBAND_RINGTONE | + AG_FEATURE_REJECT_A_CALL | + AG_FEATURE_ENHANCED_CALL_STATUS | + AG_FEATURE_ENHANCED_CALL_CONTROL | + AG_FEATURE_EXTENDED_ERROR_RESULT_CODES | + AG_FEATURE_THREE_WAY_CALLING; + int i; + + DBG(""); + + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + + add_watch(NULL, NULL, CSD_CALL_INTERFACE, NULL); + add_watch(NULL, NULL, CSD_CALL_INSTANCE, NULL); + add_watch(NULL, NULL, CSD_CALL_CONFERENCE, NULL); + add_watch(NULL, NULL, CSD_CSNET_REGISTRATION, "RegistrationChanged"); + add_watch(NULL, NULL, CSD_CSNET_OPERATOR, "OperatorNameChanged"); + add_watch(NULL, NULL, CSD_CSNET_SIGNAL, "SignalBarsChanged"); + add_watch(NULL, NULL, SSC_DBUS_IFACE, "modem_state_changed_ind"); + + if (send_method_call(SSC_DBUS_NAME, SSC_DBUS_PATH, SSC_DBUS_IFACE, + "get_modem_state", modem_state_reply, + NULL, DBUS_TYPE_INVALID) < 0) + error("Unable to send " SSC_DBUS_IFACE ".get_modem_state()"); + + /* Reset indicators */ + for (i = 0; maemo_indicators[i].desc != NULL; i++) { + if (g_str_equal(maemo_indicators[i].desc, "battchg")) + maemo_indicators[i].val = 5; + else + maemo_indicators[i].val = 0; + } + + telephony_ready_ind(features, maemo_indicators, BTRH_NOT_SUPPORTED, + chld_str); + if (send_method_call("org.freedesktop.Hal", + "/org/freedesktop/Hal/Manager", + "org.freedesktop.Hal.Manager", + "FindDeviceByCapability", + hal_find_device_reply, NULL, + DBUS_TYPE_STRING, &battery_cap, + DBUS_TYPE_INVALID) < 0) + error("Unable to send HAL method call"); + + return 0; +} + +static void remove_watch(gpointer data) +{ + g_dbus_remove_watch(connection, GPOINTER_TO_UINT(data)); +} + +void telephony_exit(void) +{ + DBG(""); + + g_free(net.operator_name); + net.operator_name = NULL; + + net.status = NETWORK_REG_STATUS_UNKOWN; + net.signal_bars = 0; + + g_slist_free(active_calls); + active_calls = NULL; + + g_slist_foreach(calls, (GFunc) csd_call_free, NULL); + g_slist_free(calls); + calls = NULL; + + g_slist_foreach(pending, (GFunc) pending_req_finalize, NULL); + g_slist_free(pending); + pending = NULL; + + g_slist_foreach(watches, (GFunc) remove_watch, NULL); + g_slist_free(watches); + watches = NULL; + + dbus_connection_unref(connection); + connection = NULL; + + telephony_deinit(); +} |