/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2008-2009 Nokia Corporation
* Copyright (C) 2004-2009 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 "logging.h"
#include "telephony.h"
/* libcsnet D-Bus definitions */
#define NETWORK_BUS_NAME "com.nokia.phone.net"
#define NETWORK_INTERFACE "Phone.Net"
#define NETWORK_PATH "/com/nokia/phone/net"
/* Mask bits for supported services */
#define NETWORK_MASK_GPRS_SUPPORT 0x01
#define NETWORK_MASK_CS_SERVICES 0x02
#define NETWORK_MASK_EGPRS_SUPPORT 0x04
#define NETWORK_MASK_HSDPA_AVAIL 0x08
#define NETWORK_MASK_HSUPA_AVAIL 0x10
/* network get cell info: cell type */
#define NETWORK_UNKNOWN_CELL 0
#define NETWORK_GSM_CELL 1
#define NETWORK_WCDMA_CELL 2
enum net_registration_status {
NETWORK_REG_STATUS_HOME = 0x00,
NETWORK_REG_STATUS_ROAM,
NETWORK_REG_STATUS_ROAM_BLINK,
NETWORK_REG_STATUS_NOSERV,
NETWORK_REG_STATUS_NOSERV_SEARCHING,
NETWORK_REG_STATUS_NOSERV_NOTSEARCHING,
NETWORK_REG_STATUS_NOSERV_NOSIM,
NETWORK_REG_STATUS_POWER_OFF = 0x08,
NETWORK_REG_STATUS_NSPS,
NETWORK_REG_STATUS_NSPS_NO_COVERAGE,
NETWORK_REG_STATUS_NOSERV_SIM_REJECTED_BY_NW
};
enum network_types {
NETWORK_GSM_HOME_PLMN = 0,
NETWORK_GSM_PREFERRED_PLMN,
NETWORK_GSM_FORBIDDEN_PLMN,
NETWORK_GSM_OTHER_PLMN,
NETWORK_GSM_NO_PLMN_AVAIL
};
enum network_alpha_tag_name_type {
NETWORK_HARDCODED_LATIN_OPER_NAME = 0,
NETWORK_HARDCODED_USC2_OPER_NAME,
NETWORK_NITZ_SHORT_OPER_NAME,
NETWORK_NITZ_FULL_OPER_NAME,
};
#define TELEPHONY_MAEMO_PATH "/com/nokia/MaemoTelephony"
#define TELEPHONY_MAEMO_INTERFACE "com.nokia.MaemoTelephony"
#define CALLERID_BASE "/var/lib/bluetooth/maemo-callerid-"
#define ALLOWED_FLAG_FILE "/var/lib/bluetooth/maemo-callerid-allowed"
#define RESTRICTED_FLAG_FILE "/var/lib/bluetooth/maemo-callerid-restricted"
#define NONE_FLAG_FILE "/var/lib/bluetooth/maemo-callerid-none"
static uint32_t callerid = 0;
/* 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"
/* 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 SIM_PHONEBOOK_BUS_NAME "com.nokia.phone.SIM"
#define SIM_PHONEBOOK_INTERFACE "Phone.Sim.Phonebook"
#define SIM_PHONEBOOK_PATH "/com/nokia/phone/SIM/phonebook"
#define PHONEBOOK_INDEX_FIRST_ENTRY 0xFFFF
#define PHONEBOOK_INDEX_NEXT_FREE_LOCATION 0xFFFE
enum sim_phonebook_type {
SIM_PHONEBOOK_TYPE_ADN = 0x0,
SIM_PHONEBOOK_TYPE_SDN,
SIM_PHONEBOOK_TYPE_FDN,
SIM_PHONEBOOK_TYPE_VMBX,
SIM_PHONEBOOK_TYPE_MBDN,
SIM_PHONEBOOK_TYPE_EN,
SIM_PHONEBOOK_TYPE_MSISDN
};
enum sim_phonebook_location_type {
SIM_PHONEBOOK_LOCATION_EXACT = 0x0,
SIM_PHONEBOOK_LOCATION_NEXT
};
struct csd_call {
char *object_path;
int status;
gboolean originating;
gboolean emergency;
gboolean on_hold;
gboolean conference;
char *number;
gboolean setup;
};
static struct {
uint8_t status;
uint16_t lac;
uint32_t cell_id;
uint32_t operator_code;
uint32_t country_code;
uint8_t network_type;
uint8_t supported_services;
uint16_t signals_bar;
char *operator_name;
} net = {
.status = NETWORK_REG_STATUS_NOSERV,
.lac = 0,
.cell_id = 0,
.operator_code = 0,
.country_code = 0,
.network_type = NETWORK_GSM_NO_PLMN_AVAIL,
.supported_services = 0,
.signals_bar = 0,
.operator_name = NULL,
};
static guint csd_watch = 0;
static DBusConnection *connection = NULL;
static GSList *calls = 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 events_enabled = FALSE;
/* Supported set of call hold operations */
static const char *chld_str = "0,1,1x,2,2x,3,4";
/* Response and hold state
* -1 = none
* 0 = incoming call is put on hold in the AG
* 1 = held incoming call is accepted in the AG
* 2 = held incoming call is rejected in the AG
*/
static int response_and_hold = -1;
static char *last_dialed_number = NULL;
/* Timer for tracking call creation requests */
static guint create_request_timer = 0;
static struct indicator maemo_indicators[] =
{
{ "battchg", "0-5", 5, TRUE },
{ "signal", "0-5", 5, TRUE },
{ "service", "0,1", 1, 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_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;
debug("telephony-maemo: 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));
}
}
void telephony_device_disconnected(void *telephony_device)
{
debug("telephony-maemo: device %p disconnected", telephony_device);
events_enabled = FALSE;
}
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)
{
response_and_hold = rh;
telephony_response_and_hold_ind(response_and_hold);
telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE);
}
void telephony_last_dialed_number_req(void *telephony_device)
{
debug("telephony-maemo: last dialed number request");
if (last_dialed_number)
telephony_dial_number_req(telephony_device,
last_dialed_number);
else
telephony_last_dialed_number_rsp(telephony_device,
CME_ERROR_NOT_ALLOWED);
}
void telephony_terminate_call_req(void *telephony_device)
{
struct csd_call *call;
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;
}
if (release_call(call) < 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;
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);
dbus_pending_call_unref(call);
dbus_message_unref(msg);
return 0;
}
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)
{
uint32_t flags = callerid;
int ret;
debug("telephony-maemo: dial request to %s", number);
if (strncmp(number, "*31#", 4) == 0) {
number += 4;
flags = CALL_FLAG_PRESENTATION_ALLOWED;
} else if (strncmp(number, "#31#", 4) == 0) {
number += 4;
flags = CALL_FLAG_PRESENTATION_RESTRICTED;
} 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, "CreateWith",
NULL, NULL,
DBUS_TYPE_STRING, &number,
DBUS_TYPE_UINT32, &flags,
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;
debug("telephony-maemo: 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)
{
debug("telephony-maemo: 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;
debug("telephony-maemo: 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;
debug("telephony-maemo: 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':
foreach_call_with_status(CSD_CALL_STATUS_HOLD, release_call);
foreach_call_with_status(CSD_CALL_STATUS_WAITING,
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:
debug("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)
{
debug("telephony-maemo: 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;
debug("telephony-maemo: 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);
}
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;
}
debug("Incoming call to %s from number %s", call_path, number);
g_free(call->number);
call->number = g_strdup(number);
telephony_update_indicator(maemo_indicators, "callsetup",
EV_CALLSETUP_INCOMING);
if (find_call_with_status(CSD_CALL_STATUS_ACTIVE))
telephony_call_waiting_ind(call->number,
number_type(call->number));
else
telephony_incoming_call_ind(call->number,
number_type(call->number));
}
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;
}
debug("Outgoing call from %s to number %s", call_path, number);
g_free(call->number);
call->number = g_strdup(number);
g_free(last_dialed_number);
last_dialed_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)
{
debug("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 handle_call_status(DBusMessage *msg, const char *call_path)
{
struct csd_call *call;
dbus_uint32_t status, cause_type, cause;
int callheld = telephony_get_indicator(maemo_indicators, "callheld");
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;
}
debug("Call %s changed from %s to %s", call_path,
call_status_str[call->status], call_status_str[status]);
if (call->status == (int) status) {
debug("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:
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_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;
}
debug("Call %s %s the conference", path, joined ? "joined" : "left");
call->conference = joined;
}
static void get_operator_name_reply(DBusPendingCall *pending_call,
void *user_data)
{
DBusMessage *reply;
DBusError err;
const char *name;
dbus_int32_t net_err;
reply = dbus_pending_call_steal_reply(pending_call);
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, reply)) {
error("get_operator_name failed: %s, %s",
err.name, err.message);
dbus_error_free(&err);
goto done;
}
dbus_error_init(&err);
if (!dbus_message_get_args(reply, &err,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INT32, &net_err,
DBUS_TYPE_INVALID)) {
error("Unexpected get_operator_name reply parameters: %s, %s",
err.name, err.message);
dbus_error_free(&err);
goto done;
}
if (net_err != 0) {
error("get_operator_name failed with code %d", net_err);
goto done;
}
if (strlen(name) == 0)
goto done;
g_free(net.operator_name);
net.operator_name = g_strdup(name);
debug("telephony-maemo: operator name updated: %s", name);
done:
dbus_message_unref(reply);
}
static void resolve_operator_name(uint32_t operator, uint32_t country)
{
uint8_t name_type = NETWORK_HARDCODED_LATIN_OPER_NAME;
send_method_call(NETWORK_BUS_NAME, NETWORK_PATH,
NETWORK_INTERFACE, "get_operator_name",
get_operator_name_reply, NULL,
DBUS_TYPE_BYTE, &name_type,
DBUS_TYPE_UINT32, &operator,
DBUS_TYPE_UINT32, &country,
DBUS_TYPE_INVALID);
}
static void update_registration_status(uint8_t status, uint16_t lac,
uint32_t cell_id,
uint32_t operator_code,
uint32_t country_code,
uint8_t network_type,
uint8_t supported_services)
{
if (net.status != status) {
switch (status) {
case NETWORK_REG_STATUS_HOME:
telephony_update_indicator(maemo_indicators, "roam",
EV_ROAM_INACTIVE);
if (net.status >= NETWORK_REG_STATUS_NOSERV)
telephony_update_indicator(maemo_indicators,
"service",
EV_SERVICE_PRESENT);
break;
case NETWORK_REG_STATUS_ROAM:
case NETWORK_REG_STATUS_ROAM_BLINK:
telephony_update_indicator(maemo_indicators, "roam",
EV_ROAM_ACTIVE);
if (net.status >= NETWORK_REG_STATUS_NOSERV)
telephony_update_indicator(maemo_indicators,
"service",
EV_SERVICE_PRESENT);
break;
case NETWORK_REG_STATUS_NOSERV:
case NETWORK_REG_STATUS_NOSERV_SEARCHING:
case NETWORK_REG_STATUS_NOSERV_NOTSEARCHING:
case NETWORK_REG_STATUS_NOSERV_NOSIM:
case NETWORK_REG_STATUS_POWER_OFF:
case NETWORK_REG_STATUS_NSPS:
case NETWORK_REG_STATUS_NSPS_NO_COVERAGE:
case NETWORK_REG_STATUS_NOSERV_SIM_REJECTED_BY_NW:
if (net.status < NETWORK_REG_STATUS_NOSERV)
telephony_update_indicator(maemo_indicators,
"service",
EV_SERVICE_NONE);
break;
}
net.status = status;
}
net.lac = lac;
net.cell_id = cell_id;
if (net.operator_code != operator_code ||
net.country_code != country_code) {
g_free(net.operator_name);
net.operator_name = NULL;
resolve_operator_name(operator_code, country_code);
net.operator_code = operator_code;
net.country_code = country_code;
}
net.network_type = network_type;
net.supported_services = supported_services;
}
static void handle_registration_status_change(DBusMessage *msg)
{
uint8_t status;
dbus_uint16_t lac, network_type, supported_services;
dbus_uint32_t cell_id, operator_code, country_code;
if (!dbus_message_get_args(msg, NULL,
DBUS_TYPE_BYTE, &status,
DBUS_TYPE_UINT16, &lac,
DBUS_TYPE_UINT32, &cell_id,
DBUS_TYPE_UINT32, &operator_code,
DBUS_TYPE_UINT32, &country_code,
DBUS_TYPE_BYTE, &network_type,
DBUS_TYPE_BYTE, &supported_services,
DBUS_TYPE_INVALID)) {
error("Unexpected parameters in registration_status_change");
return;
}
update_registration_status(status, lac, cell_id, operator_code,
country_code, network_type,
supported_services);
}
static void update_signal_strength(uint8_t signals_bar)
{
int signal;
if (signals_bar > 100) {
debug("signals_bar greater than expected: %u", signals_bar);
signals_bar = 100;
}
if (net.signals_bar == signals_bar)
return;
/* A simple conversion from 0-100 to 0-5 (used by HFP) */
signal = (signals_bar + 20) / 21;
telephony_update_indicator(maemo_indicators, "signal", signal);
net.signals_bar = signals_bar;
debug("Signal strength updated: %u/100, %d/5", signals_bar, signal);
}
static void handle_signal_strength_change(DBusMessage *msg)
{
uint8_t signals_bar, rssi_in_dbm;
if (!dbus_message_get_args(msg, NULL,
DBUS_TYPE_BYTE, &signals_bar,
DBUS_TYPE_BYTE, &rssi_in_dbm,
DBUS_TYPE_INVALID)) {
error("Unexpected parameters in signal_strength_change");
return;
}
update_signal_strength(signals_bar);
}
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;
}
dbus_message_get_args(reply, NULL,
DBUS_TYPE_INT32, &level,
DBUS_TYPE_INVALID);
*value = (int) level;
if (value == &battchg_last)
debug("telephony-maemo: battery.charge_level.last_full is %d",
*value);
else if (value == &battchg_design)
debug("telephony-maemo: battery.charge_level.design is %d",
*value);
else
debug("telephony-maemo: 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);
}
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 DBusHandlerResult signal_filter(DBusConnection *conn,
DBusMessage *msg, void *data)
{
const char *path = dbus_message_get_path(msg);
if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
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, NETWORK_INTERFACE,
"registration_status_change"))
handle_registration_status_change(msg);
else if (dbus_message_is_signal(msg, NETWORK_INTERFACE,
"signal_strength_change"))
handle_signal_strength_change(msg);
else if (dbus_message_is_signal(msg, "org.freedesktop.Hal.Device",
"PropertyModified"))
handle_hal_property_modified(msg);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
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);
call->status = (int) status;
calls = g_slist_append(calls, call);
debug("telephony-maemo: new csd call instance at %s",
object_path);
}
if (call->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);
} while (dbus_message_iter_next(iter));
}
static void signal_strength_reply(DBusPendingCall *call, void *user_data)
{
DBusError err;
DBusMessage *reply;
uint8_t signals_bar, rssi_in_dbm;
dbus_int32_t net_err;
reply = dbus_pending_call_steal_reply(call);
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, reply)) {
error("Unable to get signal strength: %s, %s",
err.name, err.message);
dbus_error_free(&err);
goto done;
}
dbus_error_init(&err);
if (!dbus_message_get_args(reply, NULL,
DBUS_TYPE_BYTE, &signals_bar,
DBUS_TYPE_BYTE, &rssi_in_dbm,
DBUS_TYPE_INT32, &net_err,
DBUS_TYPE_INVALID)) {
error("Unable to parse signal_strength reply: %s, %s",
err.name, err.message);
dbus_error_free(&err);
return;
}
if (net_err != 0) {
error("get_signal_strength failed with code %d", net_err);
return;
}
update_signal_strength(signals_bar);
done:
dbus_message_unref(reply);
}
static int get_signal_strength(void)
{
return send_method_call(NETWORK_BUS_NAME, NETWORK_PATH,
NETWORK_INTERFACE, "get_signal_strength",
signal_strength_reply, NULL,
DBUS_TYPE_INVALID);
}
static void registration_status_reply(DBusPendingCall *call, void *user_data)
{
DBusError err;
DBusMessage *reply;
uint8_t status;
dbus_uint16_t lac, network_type, supported_services;
dbus_uint32_t cell_id, operator_code, country_code;
dbus_int32_t net_err;
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;
reply = dbus_pending_call_steal_reply(call);
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, reply)) {
error("Unable to get registration status: %s, %s",
err.name, err.message);
dbus_error_free(&err);
goto done;
}
dbus_error_init(&err);
if (!dbus_message_get_args(reply, NULL,
DBUS_TYPE_BYTE, &status,
DBUS_TYPE_UINT16, &lac,
DBUS_TYPE_UINT32, &cell_id,
DBUS_TYPE_UINT32, &operator_code,
DBUS_TYPE_UINT32, &country_code,
DBUS_TYPE_BYTE, &network_type,
DBUS_TYPE_BYTE, &supported_services,
DBUS_TYPE_INT32, &net_err,
DBUS_TYPE_INVALID)) {
error("Unable to parse registration_status_change reply:"
" %s, %s", err.name, err.message);
dbus_error_free(&err);
return;
}
if (net_err != 0) {
error("get_registration_status failed with code %d", net_err);
return;
}
update_registration_status(status, lac, cell_id, operator_code,
country_code, network_type,
supported_services);
telephony_ready_ind(features, maemo_indicators, response_and_hold,
chld_str);
get_signal_strength();
done:
dbus_message_unref(reply);
}
static int get_registration_status(void)
{
return send_method_call(NETWORK_BUS_NAME, NETWORK_PATH,
NETWORK_INTERFACE, "get_registration_status",
registration_status_reply, NULL,
DBUS_TYPE_INVALID);
}
static void call_info_reply(DBusPendingCall *call, void *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_ARRAY) {
error("Unexpected signature in GetCallInfoAll return");
goto done;
}
dbus_message_iter_recurse(&iter, &sub);
parse_call_list(&sub);
get_registration_status();
done:
dbus_message_unref(reply);
}
static void hal_find_device_reply(DBusPendingCall *call, void *user_data)
{
DBusError err;
DBusMessage *reply;
DBusMessageIter iter, sub;
const char *path;
char match_string[256];
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 GetCallInfoAll 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);
debug("telephony-maemo: found battery device at %s", path);
snprintf(match_string, sizeof(match_string),
"type='signal',"
"path='%s',"
"interface='org.freedesktop.Hal.Device',"
"member='PropertyModified'", path);
dbus_bus_add_match(connection, match_string, NULL);
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);
}
static void phonebook_read_reply(DBusPendingCall *call, void *user_data)
{
DBusError derr;
DBusMessage *reply;
const char *name, *number;
char **number_type = user_data;
dbus_int32_t current_location, err;
reply = dbus_pending_call_steal_reply(call);
dbus_error_init(&derr);
if (dbus_set_error_from_message(&derr, reply)) {
error("SIM.Phonebook replied with an error: %s, %s",
derr.name, derr.message);
dbus_error_free(&derr);
goto done;
}
dbus_error_init(&derr);
dbus_message_get_args(reply, NULL,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_STRING, &number,
DBUS_TYPE_INT32, ¤t_location,
DBUS_TYPE_INT32, &err,
DBUS_TYPE_INVALID);
if (dbus_error_is_set(&derr)) {
error("Unable to parse SIM.Phonebook.read arguments: %s, %s",
derr.name, derr.message);
dbus_error_free(&derr);
goto done;
}
if (err != 0) {
error("SIM.Phonebook.read failed with error %d", err);
if (number_type == &vmbx)
vmbx = g_strdup(getenv("VMBX_NUMBER"));
goto done;
}
if (number_type == &msisdn) {
g_free(msisdn);
msisdn = g_strdup(number);
debug("Got MSISDN %s (%s)", number, name);
} else {
g_free(vmbx);
vmbx = g_strdup(number);
debug("Got voice mailbox number %s (%s)", number, name);
}
done:
dbus_message_unref(reply);
}
static gboolean csd_init(gpointer user_data)
{
char match_string[128];
const char *battery_cap = "battery";
dbus_uint32_t location;
uint8_t pb_type, location_type;
int ret;
if (!dbus_connection_add_filter(connection, signal_filter,
NULL, NULL)) {
error("Can't add signal filter");
return FALSE;
}
snprintf(match_string, sizeof(match_string),
"type=signal,interface=%s", CSD_CALL_INTERFACE);
dbus_bus_add_match(connection, match_string, NULL);
snprintf(match_string, sizeof(match_string),
"type=signal,interface=%s", CSD_CALL_INSTANCE);
dbus_bus_add_match(connection, match_string, NULL);
snprintf(match_string, sizeof(match_string),
"type=signal,interface=%s", CSD_CALL_CONFERENCE);
dbus_bus_add_match(connection, match_string, NULL);
snprintf(match_string, sizeof(match_string),
"type=signal,interface=%s", NETWORK_INTERFACE);
dbus_bus_add_match(connection, match_string, NULL);
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 FALSE;
}
ret = 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);
if (ret < 0) {
error("Unable to send HAL method call");
return FALSE;
}
pb_type = SIM_PHONEBOOK_TYPE_MSISDN;
location = PHONEBOOK_INDEX_FIRST_ENTRY;
location_type = SIM_PHONEBOOK_LOCATION_NEXT;
ret = send_method_call(SIM_PHONEBOOK_BUS_NAME, SIM_PHONEBOOK_PATH,
SIM_PHONEBOOK_INTERFACE, "read",
phonebook_read_reply, &msisdn,
DBUS_TYPE_BYTE, &pb_type,
DBUS_TYPE_INT32, &location,
DBUS_TYPE_BYTE, &location_type,
DBUS_TYPE_INVALID);
if (ret < 0) {
error("Unable to send " SIM_PHONEBOOK_INTERFACE ".read()");
return FALSE;
}
pb_type = SIM_PHONEBOOK_TYPE_VMBX;
location = PHONEBOOK_INDEX_FIRST_ENTRY;
location_type = SIM_PHONEBOOK_LOCATION_NEXT;
ret = send_method_call(SIM_PHONEBOOK_BUS_NAME, SIM_PHONEBOOK_PATH,
SIM_PHONEBOOK_INTERFACE, "read",
phonebook_read_reply, &vmbx,
DBUS_TYPE_BYTE, &pb_type,
DBUS_TYPE_INT32, &location,
DBUS_TYPE_BYTE, &location_type,
DBUS_TYPE_INVALID);
if (ret < 0) {
error("Unable to send " SIM_PHONEBOOK_INTERFACE ".read()");
return FALSE;
}
return FALSE;
}
static void csd_ready(DBusConnection *conn, void *user_data)
{
g_dbus_remove_watch(conn, csd_watch);
csd_watch = 0;
g_timeout_add_seconds(2, csd_init, NULL);
}
static inline DBusMessage *invalid_args(DBusMessage *msg)
{
return g_dbus_create_error(msg,"org.bluez.Error.InvalidArguments",
"Invalid arguments in method call");
}
static uint32_t get_callflag(const char *callerid_setting)
{
if (callerid_setting != NULL) {
if (g_str_equal(callerid_setting, "allowed"))
return CALL_FLAG_PRESENTATION_ALLOWED;
else if (g_str_equal(callerid_setting, "restricted"))
return CALL_FLAG_PRESENTATION_RESTRICTED;
else
return CALL_FLAG_NONE;
} else
return CALL_FLAG_NONE;
}
static void generate_flag_file(const char *filename)
{
int fd;
if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS) ||
g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS) ||
g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS))
return;
fd = open(filename, O_WRONLY | O_CREAT, 0);
if (fd >= 0)
close(fd);
}
static void save_callerid_to_file(const char *callerid_setting)
{
char callerid_file[FILENAME_MAX];
snprintf(callerid_file, sizeof(callerid_file), "%s%s",
CALLERID_BASE, callerid_setting);
if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS))
rename(ALLOWED_FLAG_FILE, callerid_file);
else if (g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS))
rename(RESTRICTED_FLAG_FILE, callerid_file);
else if (g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS))
rename(NONE_FLAG_FILE, callerid_file);
else
generate_flag_file(callerid_file);
}
static uint32_t callerid_from_file(void)
{
if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS))
return CALL_FLAG_PRESENTATION_ALLOWED;
else if (g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS))
return CALL_FLAG_PRESENTATION_RESTRICTED;
else if (g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS))
return CALL_FLAG_NONE;
else
return CALL_FLAG_NONE;
}
static DBusMessage *set_callerid(DBusConnection *conn, DBusMessage *msg,
void *data)
{
const char *callerid_setting;
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING,
&callerid_setting,
DBUS_TYPE_INVALID) == FALSE)
return invalid_args(msg);
if (g_str_equal(callerid_setting, "allowed") ||
g_str_equal(callerid_setting, "restricted") ||
g_str_equal(callerid_setting, "none")) {
save_callerid_to_file(callerid_setting);
callerid = get_callflag(callerid_setting);
debug("telephony-maemo setting callerid flag: %s",
callerid_setting);
return dbus_message_new_method_return(msg);
}
error("telephony-maemo: invalid argument %s for method call"
" SetCallerId", callerid_setting);
return invalid_args(msg);
}
static GDBusMethodTable telephony_maemo_methods[] = {
{"SetCallerId", "s", "", set_callerid,
G_DBUS_METHOD_FLAG_ASYNC},
{ }
};
int telephony_init(void)
{
connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
csd_watch = g_dbus_add_service_watch(connection, CSD_CALL_BUS_NAME,
csd_ready, NULL, NULL, NULL);
if (dbus_bus_name_has_owner(connection, CSD_CALL_BUS_NAME, NULL))
csd_ready(connection, NULL);
else
info("CSD not yet available. Waiting for it...");
generate_flag_file(NONE_FLAG_FILE);
callerid = callerid_from_file();
if (!g_dbus_register_interface(connection, TELEPHONY_MAEMO_PATH,
TELEPHONY_MAEMO_INTERFACE, telephony_maemo_methods,
NULL, NULL, NULL, NULL)) {
error("telephony-maemo interface %s init failed on path %s",
TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH);
}
debug("telephony-maemo registering %s interface on path %s",
TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH);
return 0;
}
void telephony_exit(void)
{
if (csd_watch) {
g_dbus_remove_watch(connection, csd_watch);
csd_watch = 0;
}
g_slist_foreach(calls, (GFunc) csd_call_free, NULL);
g_slist_free(calls);
calls = NULL;
dbus_connection_remove_filter(connection, signal_filter, NULL);
dbus_connection_unref(connection);
connection = NULL;
}