bluez/audio/telephony-ofono.c
2012-04-16 18:23:02 +02:00

1638 lines
39 KiB
C

/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2009-2010 Intel Corporation
* Copyright (C) 2006-2009 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 <string.h>
#include <stdint.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus.h>
#include <bluetooth/sdp.h>
#include "log.h"
#include "telephony.h"
enum net_registration_status {
NETWORK_REG_STATUS_HOME = 0x00,
NETWORK_REG_STATUS_ROAM,
NETWORK_REG_STATUS_NOSERV
};
struct voice_call {
char *obj_path;
int status;
gboolean originating;
gboolean conference;
char *number;
guint watch;
};
static DBusConnection *connection = NULL;
static char *modem_obj_path = NULL;
static char *last_dialed_number = NULL;
static GSList *calls = NULL;
static GSList *watches = NULL;
static GSList *pending = NULL;
#define OFONO_BUS_NAME "org.ofono"
#define OFONO_PATH "/"
#define OFONO_MODEM_INTERFACE "org.ofono.Modem"
#define OFONO_MANAGER_INTERFACE "org.ofono.Manager"
#define OFONO_NETWORKREG_INTERFACE "org.ofono.NetworkRegistration"
#define OFONO_VCMANAGER_INTERFACE "org.ofono.VoiceCallManager"
#define OFONO_VC_INTERFACE "org.ofono.VoiceCall"
/* 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 struct {
uint8_t status;
uint32_t signals_bar;
char *operator_name;
} net = {
.status = NETWORK_REG_STATUS_NOSERV,
.signals_bar = 0,
.operator_name = NULL,
};
static const char *chld_str = "0,1,1x,2,2x,3,4";
static char *subscriber_number = NULL;
static gboolean events_enabled = FALSE;
static struct indicator ofono_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 struct voice_call *find_vc(const char *path)
{
GSList *l;
for (l = calls; l != NULL; l = l->next) {
struct voice_call *vc = l->data;
if (g_str_equal(vc->obj_path, path))
return vc;
}
return NULL;
}
static struct voice_call *find_vc_with_status(int status)
{
GSList *l;
for (l = calls; l != NULL; l = l->next) {
struct voice_call *vc = l->data;
if (vc->status == status)
return vc;
}
return NULL;
}
static struct voice_call *find_vc_without_status(int status)
{
GSList *l;
for (l = calls; l != NULL; l = l->next) {
struct voice_call *call = l->data;
if (call->status != status)
return call;
}
return NULL;
}
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 voice_call *coming;
DBG("telephony-ofono: device %p connected", telephony_device);
coming = find_vc_with_status(CALL_STATUS_ALERTING);
if (coming) {
if (find_vc_with_status(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)
{
DBG("telephony-ofono: 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)
{
telephony_response_and_hold_rsp(telephony_device,
CME_ERROR_NOT_SUPPORTED);
}
void telephony_last_dialed_number_req(void *telephony_device)
{
DBG("telephony-ofono: 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);
}
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);
pending = g_slist_prepend(pending, call);
dbus_message_unref(msg);
return 0;
}
static int answer_call(struct voice_call *vc)
{
DBG("%s", vc->number);
return send_method_call(OFONO_BUS_NAME, vc->obj_path,
OFONO_VC_INTERFACE, "Answer",
NULL, NULL, DBUS_TYPE_INVALID);
}
static int release_call(struct voice_call *vc)
{
DBG("%s", vc->number);
return send_method_call(OFONO_BUS_NAME, vc->obj_path,
OFONO_VC_INTERFACE, "Hangup",
NULL, NULL, DBUS_TYPE_INVALID);
}
static int release_answer_calls(void)
{
DBG("");
return send_method_call(OFONO_BUS_NAME, modem_obj_path,
OFONO_VCMANAGER_INTERFACE,
"ReleaseAndAnswer",
NULL, NULL, DBUS_TYPE_INVALID);
}
static int split_call(struct voice_call *call)
{
DBG("%s", call->number);
return send_method_call(OFONO_BUS_NAME, modem_obj_path,
OFONO_VCMANAGER_INTERFACE,
"PrivateChat",
NULL, NULL,
DBUS_TYPE_OBJECT_PATH,
call->obj_path,
DBUS_TYPE_INVALID);
return -1;
}
static int swap_calls(void)
{
DBG("");
return send_method_call(OFONO_BUS_NAME, modem_obj_path,
OFONO_VCMANAGER_INTERFACE,
"SwapCalls",
NULL, NULL, DBUS_TYPE_INVALID);
}
static int create_conference(void)
{
DBG("");
return send_method_call(OFONO_BUS_NAME, modem_obj_path,
OFONO_VCMANAGER_INTERFACE,
"CreateMultiparty",
NULL, NULL, DBUS_TYPE_INVALID);
}
static int release_conference(void)
{
DBG("");
return send_method_call(OFONO_BUS_NAME, modem_obj_path,
OFONO_VCMANAGER_INTERFACE,
"HangupMultiparty",
NULL, NULL, DBUS_TYPE_INVALID);
}
static int call_transfer(void)
{
DBG("");
return send_method_call(OFONO_BUS_NAME, modem_obj_path,
OFONO_VCMANAGER_INTERFACE,
"Transfer",
NULL, NULL, DBUS_TYPE_INVALID);
}
void telephony_terminate_call_req(void *telephony_device)
{
struct voice_call *call;
struct voice_call *alerting;
int err;
call = find_vc_with_status(CALL_STATUS_ACTIVE);
if (!call)
call = calls->data;
if (!call) {
error("No active call");
telephony_terminate_call_rsp(telephony_device,
CME_ERROR_NOT_ALLOWED);
return;
}
alerting = find_vc_with_status(CALL_STATUS_ALERTING);
if (call->status == CALL_STATUS_HELD && 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 voice_call *vc;
int ret;
vc = find_vc_with_status(CALL_STATUS_INCOMING);
if (!vc)
vc = find_vc_with_status(CALL_STATUS_ALERTING);
if (!vc)
vc = find_vc_with_status(CALL_STATUS_WAITING);
if (!vc) {
telephony_answer_call_rsp(telephony_device,
CME_ERROR_NOT_ALLOWED);
return;
}
ret = answer_call(vc);
if (ret < 0) {
telephony_answer_call_rsp(telephony_device,
CME_ERROR_AG_FAILURE);
return;
}
telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
}
void telephony_dial_number_req(void *telephony_device, const char *number)
{
const char *clir;
int ret;
DBG("telephony-ofono: dial request to %s", number);
if (!modem_obj_path) {
telephony_dial_number_rsp(telephony_device,
CME_ERROR_AG_FAILURE);
return;
}
if (!strncmp(number, "*31#", 4)) {
number += 4;
clir = "enabled";
} else if (!strncmp(number, "#31#", 4)) {
number += 4;
clir = "disabled";
} else
clir = "default";
ret = send_method_call(OFONO_BUS_NAME, modem_obj_path,
OFONO_VCMANAGER_INTERFACE,
"Dial", NULL, NULL,
DBUS_TYPE_STRING, &number,
DBUS_TYPE_STRING, &clir,
DBUS_TYPE_INVALID);
if (ret < 0)
telephony_dial_number_rsp(telephony_device,
CME_ERROR_AG_FAILURE);
else
telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);
}
void telephony_transmit_dtmf_req(void *telephony_device, char tone)
{
char *tone_string;
int ret;
DBG("telephony-ofono: transmit dtmf: %c", tone);
if (!modem_obj_path) {
telephony_transmit_dtmf_rsp(telephony_device,
CME_ERROR_AG_FAILURE);
return;
}
tone_string = g_strdup_printf("%c", tone);
ret = send_method_call(OFONO_BUS_NAME, modem_obj_path,
OFONO_VCMANAGER_INTERFACE,
"SendTones", NULL, NULL,
DBUS_TYPE_STRING, &tone_string,
DBUS_TYPE_INVALID);
g_free(tone_string);
if (ret < 0)
telephony_transmit_dtmf_rsp(telephony_device,
CME_ERROR_AG_FAILURE);
else
telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE);
}
void telephony_subscriber_number_req(void *telephony_device)
{
DBG("telephony-ofono: subscriber number request");
if (subscriber_number)
telephony_subscriber_number_ind(subscriber_number,
NUMBER_TYPE_TELEPHONY,
SUBSCRIBER_SERVICE_VOICE);
telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE);
}
void telephony_list_current_calls_req(void *telephony_device)
{
GSList *l;
int i;
DBG("telephony-ofono: list current calls request");
for (l = calls, i = 1; l != NULL; l = l->next, i++) {
struct voice_call *vc = l->data;
int direction, multiparty;
direction = vc->originating ?
CALL_DIR_OUTGOING : CALL_DIR_INCOMING;
multiparty = vc->conference ?
CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO;
DBG("call %s direction %d multiparty %d", vc->number,
direction, multiparty);
telephony_list_current_call_ind(i, direction, vc->status,
CALL_MODE_VOICE, multiparty,
vc->number, number_type(vc->number));
}
telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
}
void telephony_operator_selection_req(void *telephony_device)
{
DBG("telephony-ofono: operator selection request");
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_vc_with_status(int status,
int (*func)(struct voice_call *vc))
{
GSList *l;
for (l = calls; l != NULL; l = l->next) {
struct voice_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 voice_call *call;
int err = 0;
DBG("telephony-ofono: 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_vc_with_status(CALL_STATUS_WAITING))
foreach_vc_with_status(CALL_STATUS_WAITING,
release_call);
else
foreach_vc_with_status(CALL_STATUS_HELD, release_call);
break;
case '1':
if (idx) {
if (call)
err = release_call(call);
break;
}
err = release_answer_calls();
break;
case '2':
if (idx) {
if (call)
err = split_call(call);
} else {
call = find_vc_with_status(CALL_STATUS_WAITING);
if (call)
err = answer_call(call);
else
err = swap_calls();
}
break;
case '3':
if (find_vc_with_status(CALL_STATUS_HELD) ||
find_vc_with_status(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-ofono: 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 voice_call *active, *incoming;
int err;
DBG("telephony-ofono: got key press request for %s", keys);
incoming = find_vc_with_status(CALL_STATUS_INCOMING);
active = find_vc_with_status(CALL_STATUS_ACTIVE);
if (incoming)
err = answer_call(incoming);
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-ofono: got %s voice dial request",
enable ? "enable" : "disable");
telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED);
}
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 call_free(void *data)
{
struct voice_call *vc = data;
DBG("%s", vc->obj_path);
if (vc->status == CALL_STATUS_ACTIVE)
telephony_update_indicator(ofono_indicators, "call",
EV_CALL_INACTIVE);
else
telephony_update_indicator(ofono_indicators, "callsetup",
EV_CALLSETUP_INACTIVE);
if (vc->status == CALL_STATUS_INCOMING)
telephony_calling_stopped_ind();
g_dbus_remove_watch(connection, vc->watch);
g_free(vc->obj_path);
g_free(vc->number);
g_free(vc);
}
static gboolean handle_vc_property_changed(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct voice_call *vc = data;
const char *obj_path = dbus_message_get_path(msg);
DBusMessageIter iter, sub;
const char *property, *state;
DBG("path %s", obj_path);
dbus_message_iter_init(msg, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
error("Unexpected signature in vc PropertyChanged signal");
return TRUE;
}
dbus_message_iter_get_basic(&iter, &property);
DBG("property %s", property);
dbus_message_iter_next(&iter);
dbus_message_iter_recurse(&iter, &sub);
if (g_str_equal(property, "State")) {
dbus_message_iter_get_basic(&sub, &state);
DBG("State %s", state);
if (g_str_equal(state, "disconnected")) {
calls = g_slist_remove(calls, vc);
call_free(vc);
} else if (g_str_equal(state, "active")) {
telephony_update_indicator(ofono_indicators,
"call", EV_CALL_ACTIVE);
telephony_update_indicator(ofono_indicators,
"callsetup",
EV_CALLSETUP_INACTIVE);
if (vc->status == CALL_STATUS_INCOMING)
telephony_calling_stopped_ind();
vc->status = CALL_STATUS_ACTIVE;
} else if (g_str_equal(state, "alerting")) {
telephony_update_indicator(ofono_indicators,
"callsetup", EV_CALLSETUP_ALERTING);
vc->status = CALL_STATUS_ALERTING;
vc->originating = TRUE;
} else if (g_str_equal(state, "incoming")) {
/* state change from waiting to incoming */
telephony_update_indicator(ofono_indicators,
"callsetup", EV_CALLSETUP_INCOMING);
telephony_incoming_call_ind(vc->number,
NUMBER_TYPE_TELEPHONY);
vc->status = CALL_STATUS_INCOMING;
vc->originating = FALSE;
} else if (g_str_equal(state, "held")) {
vc->status = CALL_STATUS_HELD;
if (find_vc_without_status(CALL_STATUS_HELD))
telephony_update_indicator(ofono_indicators,
"callheld",
EV_CALLHELD_MULTIPLE);
else
telephony_update_indicator(ofono_indicators,
"callheld",
EV_CALLHELD_ON_HOLD);
}
} else if (g_str_equal(property, "Multiparty")) {
dbus_bool_t multiparty;
dbus_message_iter_get_basic(&sub, &multiparty);
DBG("Multiparty %s", multiparty ? "True" : "False");
vc->conference = multiparty;
}
return TRUE;
}
static struct voice_call *call_new(const char *path, DBusMessageIter *properties)
{
struct voice_call *vc;
DBG("%s", path);
vc = g_new0(struct voice_call, 1);
vc->obj_path = g_strdup(path);
vc->watch = g_dbus_add_signal_watch(connection, NULL, path,
OFONO_VC_INTERFACE, "PropertyChanged",
handle_vc_property_changed, vc, NULL);
while (dbus_message_iter_get_arg_type(properties)
== DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter entry, value;
const char *property, *cli, *state;
dbus_bool_t multiparty;
dbus_message_iter_recurse(properties, &entry);
dbus_message_iter_get_basic(&entry, &property);
dbus_message_iter_next(&entry);
dbus_message_iter_recurse(&entry, &value);
if (g_str_equal(property, "LineIdentification")) {
dbus_message_iter_get_basic(&value, &cli);
DBG("cli %s", cli);
vc->number = g_strdup(cli);
} else if (g_str_equal(property, "State")) {
dbus_message_iter_get_basic(&value, &state);
DBG("state %s", state);
if (g_str_equal(state, "incoming"))
vc->status = CALL_STATUS_INCOMING;
else if (g_str_equal(state, "dialing"))
vc->status = CALL_STATUS_DIALING;
else if (g_str_equal(state, "alerting"))
vc->status = CALL_STATUS_ALERTING;
else if (g_str_equal(state, "waiting"))
vc->status = CALL_STATUS_WAITING;
else if (g_str_equal(state, "held"))
vc->status = CALL_STATUS_HELD;
} else if (g_str_equal(property, "Multiparty")) {
dbus_message_iter_get_basic(&value, &multiparty);
DBG("Multipary %s", multiparty ? "True" : "False");
vc->conference = multiparty;
}
dbus_message_iter_next(properties);
}
switch (vc->status) {
case CALL_STATUS_INCOMING:
DBG("CALL_STATUS_INCOMING");
vc->originating = FALSE;
telephony_update_indicator(ofono_indicators, "callsetup",
EV_CALLSETUP_INCOMING);
telephony_incoming_call_ind(vc->number, NUMBER_TYPE_TELEPHONY);
break;
case CALL_STATUS_DIALING:
DBG("CALL_STATUS_DIALING");
vc->originating = TRUE;
g_free(last_dialed_number);
last_dialed_number = g_strdup(vc->number);
telephony_update_indicator(ofono_indicators, "callsetup",
EV_CALLSETUP_OUTGOING);
break;
case CALL_STATUS_ALERTING:
DBG("CALL_STATUS_ALERTING");
vc->originating = TRUE;
g_free(last_dialed_number);
last_dialed_number = g_strdup(vc->number);
telephony_update_indicator(ofono_indicators, "callsetup",
EV_CALLSETUP_ALERTING);
break;
case CALL_STATUS_WAITING:
DBG("CALL_STATUS_WAITING");
vc->originating = FALSE;
telephony_update_indicator(ofono_indicators, "callsetup",
EV_CALLSETUP_INCOMING);
telephony_call_waiting_ind(vc->number, NUMBER_TYPE_TELEPHONY);
break;
}
return vc;
}
static void remove_pending(DBusPendingCall *call)
{
pending = g_slist_remove(pending, call);
dbus_pending_call_unref(call);
}
static void call_added(const char *path, DBusMessageIter *properties)
{
struct voice_call *vc;
DBG("%s", path);
vc = find_vc(path);
if (vc)
return;
vc = call_new(path, properties);
calls = g_slist_prepend(calls, vc);
}
static void get_calls_reply(DBusPendingCall *call, void *user_data)
{
DBusError err;
DBusMessage *reply;
DBusMessageIter iter, entry;
DBG("");
reply = dbus_pending_call_steal_reply(call);
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, reply)) {
error("ofono 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");
goto done;
}
dbus_message_iter_recurse(&iter, &entry);
while (dbus_message_iter_get_arg_type(&entry)
== DBUS_TYPE_STRUCT) {
const char *path;
DBusMessageIter value, properties;
dbus_message_iter_recurse(&entry, &value);
dbus_message_iter_get_basic(&value, &path);
dbus_message_iter_next(&value);
dbus_message_iter_recurse(&value, &properties);
call_added(path, &properties);
dbus_message_iter_next(&entry);
}
done:
dbus_message_unref(reply);
remove_pending(call);
}
static void handle_network_property(const char *property, DBusMessageIter *variant)
{
const char *status, *operator;
unsigned int signals_bar;
if (g_str_equal(property, "Status")) {
dbus_message_iter_get_basic(variant, &status);
DBG("Status is %s", status);
if (g_str_equal(status, "registered")) {
net.status = NETWORK_REG_STATUS_HOME;
telephony_update_indicator(ofono_indicators,
"roam", EV_ROAM_INACTIVE);
telephony_update_indicator(ofono_indicators,
"service", EV_SERVICE_PRESENT);
} else if (g_str_equal(status, "roaming")) {
net.status = NETWORK_REG_STATUS_ROAM;
telephony_update_indicator(ofono_indicators,
"roam", EV_ROAM_ACTIVE);
telephony_update_indicator(ofono_indicators,
"service", EV_SERVICE_PRESENT);
} else {
net.status = NETWORK_REG_STATUS_NOSERV;
telephony_update_indicator(ofono_indicators,
"roam", EV_ROAM_INACTIVE);
telephony_update_indicator(ofono_indicators,
"service", EV_SERVICE_NONE);
}
} else if (g_str_equal(property, "Name")) {
dbus_message_iter_get_basic(variant, &operator);
DBG("Operator is %s", operator);
g_free(net.operator_name);
net.operator_name = g_strdup(operator);
} else if (g_str_equal(property, "SignalStrength")) {
dbus_message_iter_get_basic(variant, &signals_bar);
DBG("SignalStrength is %d", signals_bar);
net.signals_bar = signals_bar;
telephony_update_indicator(ofono_indicators, "signal",
(signals_bar + 20) / 21);
}
}
static int parse_network_properties(DBusMessageIter *properties)
{
int i;
/* Reset indicators */
for (i = 0; ofono_indicators[i].desc != NULL; i++) {
if (g_str_equal(ofono_indicators[i].desc, "battchg"))
ofono_indicators[i].val = 5;
else
ofono_indicators[i].val = 0;
}
while (dbus_message_iter_get_arg_type(properties)
== DBUS_TYPE_DICT_ENTRY) {
const char *key;
DBusMessageIter value, entry;
dbus_message_iter_recurse(properties, &entry);
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
dbus_message_iter_recurse(&entry, &value);
handle_network_property(key, &value);
dbus_message_iter_next(properties);
}
return 0;
}
static void get_properties_reply(DBusPendingCall *call, void *user_data)
{
DBusError err;
DBusMessage *reply;
DBusMessageIter iter, properties;
int ret = 0;
DBG("");
reply = dbus_pending_call_steal_reply(call);
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, reply)) {
error("ofono 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");
goto done;
}
dbus_message_iter_recurse(&iter, &properties);
ret = parse_network_properties(&properties);
if (ret < 0) {
error("Unable to parse %s.GetProperty reply",
OFONO_NETWORKREG_INTERFACE);
goto done;
}
ret = send_method_call(OFONO_BUS_NAME, modem_obj_path,
OFONO_VCMANAGER_INTERFACE, "GetCalls",
get_calls_reply, NULL, DBUS_TYPE_INVALID);
if (ret < 0)
error("Unable to send %s.GetCalls",
OFONO_VCMANAGER_INTERFACE);
done:
dbus_message_unref(reply);
remove_pending(call);
}
static void network_found(const char *path)
{
int ret;
DBG("%s", path);
modem_obj_path = g_strdup(path);
ret = send_method_call(OFONO_BUS_NAME, path,
OFONO_NETWORKREG_INTERFACE, "GetProperties",
get_properties_reply, NULL, DBUS_TYPE_INVALID);
if (ret < 0)
error("Unable to send %s.GetProperties",
OFONO_NETWORKREG_INTERFACE);
}
static void modem_removed(const char *path)
{
if (g_strcmp0(modem_obj_path, path) != 0)
return;
DBG("%s", path);
g_slist_free_full(calls, call_free);
calls = NULL;
g_free(net.operator_name);
net.operator_name = NULL;
net.status = NETWORK_REG_STATUS_NOSERV;
net.signals_bar = 0;
g_free(modem_obj_path);
modem_obj_path = NULL;
}
static void parse_modem_interfaces(const char *path, DBusMessageIter *ifaces)
{
DBG("%s", path);
while (dbus_message_iter_get_arg_type(ifaces) == DBUS_TYPE_STRING) {
const char *iface;
dbus_message_iter_get_basic(ifaces, &iface);
if (g_str_equal(iface, OFONO_NETWORKREG_INTERFACE)) {
network_found(path);
return;
}
dbus_message_iter_next(ifaces);
}
modem_removed(path);
}
static void modem_added(const char *path, DBusMessageIter *properties)
{
if (modem_obj_path != NULL) {
DBG("Ignoring, modem already exist");
return;
}
DBG("%s", path);
while (dbus_message_iter_get_arg_type(properties)
== DBUS_TYPE_DICT_ENTRY) {
const char *key;
DBusMessageIter interfaces, value, entry;
dbus_message_iter_recurse(properties, &entry);
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
dbus_message_iter_recurse(&entry, &value);
if (strcasecmp(key, "Interfaces") != 0)
goto next;
if (dbus_message_iter_get_arg_type(&value)
!= DBUS_TYPE_ARRAY) {
error("Invalid Signature");
return;
}
dbus_message_iter_recurse(&value, &interfaces);
parse_modem_interfaces(path, &interfaces);
if (modem_obj_path != NULL)
return;
next:
dbus_message_iter_next(properties);
}
}
static void get_modems_reply(DBusPendingCall *call, void *user_data)
{
DBusError err;
DBusMessage *reply;
DBusMessageIter iter, entry;
DBG("");
reply = dbus_pending_call_steal_reply(call);
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, reply)) {
error("ofono replied with an error: %s, %s",
err.name, err.message);
dbus_error_free(&err);
goto done;
}
/* Skip modem selection if a modem already exist */
if (modem_obj_path != NULL)
goto done;
dbus_message_iter_init(reply, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
error("Unexpected signature");
goto done;
}
dbus_message_iter_recurse(&iter, &entry);
while (dbus_message_iter_get_arg_type(&entry)
== DBUS_TYPE_STRUCT) {
const char *path;
DBusMessageIter item, properties;
dbus_message_iter_recurse(&entry, &item);
dbus_message_iter_get_basic(&item, &path);
dbus_message_iter_next(&item);
dbus_message_iter_recurse(&item, &properties);
modem_added(path, &properties);
if (modem_obj_path != NULL)
break;
dbus_message_iter_next(&entry);
}
done:
dbus_message_unref(reply);
remove_pending(call);
}
static gboolean handle_network_property_changed(DBusConnection *conn,
DBusMessage *msg, void *data)
{
DBusMessageIter iter, variant;
const char *property;
dbus_message_iter_init(msg, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
error("Unexpected signature in networkregistration"
" PropertyChanged signal");
return TRUE;
}
dbus_message_iter_get_basic(&iter, &property);
DBG("in handle_registration_property_changed(),"
" the property is %s", property);
dbus_message_iter_next(&iter);
dbus_message_iter_recurse(&iter, &variant);
handle_network_property(property, &variant);
return TRUE;
}
static void handle_modem_property(const char *path, const char *property,
DBusMessageIter *variant)
{
DBG("%s", property);
if (g_str_equal(property, "Interfaces")) {
DBusMessageIter interfaces;
if (dbus_message_iter_get_arg_type(variant)
!= DBUS_TYPE_ARRAY) {
error("Invalid signature");
return;
}
dbus_message_iter_recurse(variant, &interfaces);
parse_modem_interfaces(path, &interfaces);
}
}
static gboolean handle_modem_property_changed(DBusConnection *conn,
DBusMessage *msg, void *data)
{
DBusMessageIter iter, variant;
const char *property, *path;
path = dbus_message_get_path(msg);
/* Ignore if modem already exist and paths doesn't match */
if (modem_obj_path != NULL &&
g_str_equal(path, modem_obj_path) == FALSE)
return TRUE;
dbus_message_iter_init(msg, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
error("Unexpected signature in %s.%s PropertyChanged signal",
dbus_message_get_interface(msg),
dbus_message_get_member(msg));
return TRUE;
}
dbus_message_iter_get_basic(&iter, &property);
dbus_message_iter_next(&iter);
dbus_message_iter_recurse(&iter, &variant);
handle_modem_property(path, property, &variant);
return TRUE;
}
static gboolean handle_vcmanager_call_added(DBusConnection *conn,
DBusMessage *msg, void *data)
{
DBusMessageIter iter, properties;
const char *path = dbus_message_get_path(msg);
/* Ignore call if modem path doesn't math */
if (g_strcmp0(modem_obj_path, path) != 0)
return TRUE;
dbus_message_iter_init(msg, &iter);
if (dbus_message_iter_get_arg_type(&iter)
!= DBUS_TYPE_OBJECT_PATH) {
error("Unexpected signature in %s.%s signal",
dbus_message_get_interface(msg),
dbus_message_get_member(msg));
return TRUE;
}
dbus_message_iter_get_basic(&iter, &path);
dbus_message_iter_next(&iter);
dbus_message_iter_recurse(&iter, &properties);
call_added(path, &properties);
return TRUE;
}
static void call_removed(const char *path)
{
struct voice_call *vc;
DBG("%s", path);
vc = find_vc(path);
if (vc == NULL)
return;
calls = g_slist_remove(calls, vc);
call_free(vc);
}
static gboolean handle_vcmanager_call_removed(DBusConnection *conn,
DBusMessage *msg, void *data)
{
const char *path = dbus_message_get_path(msg);
/* Ignore call if modem path doesn't math */
if (g_strcmp0(modem_obj_path, path) != 0)
return TRUE;
if (!dbus_message_get_args(msg, NULL,
DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID)) {
error("Unexpected signature in %s.%s signal",
dbus_message_get_interface(msg),
dbus_message_get_member(msg));
return TRUE;
}
call_removed(path);
return TRUE;
}
static gboolean handle_manager_modem_added(DBusConnection *conn,
DBusMessage *msg, void *data)
{
DBusMessageIter iter, properties;
const char *path;
if (modem_obj_path != NULL)
return TRUE;
dbus_message_iter_init(msg, &iter);
if (dbus_message_iter_get_arg_type(&iter)
!= DBUS_TYPE_OBJECT_PATH) {
error("Unexpected signature in %s.%s signal",
dbus_message_get_interface(msg),
dbus_message_get_member(msg));
return TRUE;
}
dbus_message_iter_get_basic(&iter, &path);
dbus_message_iter_next(&iter);
dbus_message_iter_recurse(&iter, &properties);
modem_added(path, &properties);
return TRUE;
}
static gboolean handle_manager_modem_removed(DBusConnection *conn,
DBusMessage *msg, void *data)
{
const char *path;
if (!dbus_message_get_args(msg, NULL,
DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID)) {
error("Unexpected signature in %s.%s signal",
dbus_message_get_interface(msg),
dbus_message_get_member(msg));
return TRUE;
}
modem_removed(path);
return TRUE;
}
static void hal_battery_level_reply(DBusPendingCall *call, void *user_data)
{
DBusMessage *reply;
DBusError err;
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_error_init(&err);
if (dbus_message_get_args(reply, &err,
DBUS_TYPE_INT32, &level,
DBUS_TYPE_INVALID) == FALSE) {
error("Unable to parse GetPropertyInteger reply: %s, %s",
err.name, err.message);
dbus_error_free(&err);
goto done;
}
*value = (int) level;
if (value == &battchg_last)
DBG("telephony-ofono: battery.charge_level.last_full"
" is %d", *value);
else if (value == &battchg_design)
DBG("telephony-ofono: battery.charge_level.design"
" is %d", *value);
else
DBG("telephony-ofono: 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(ofono_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 gboolean handle_hal_property_modified(DBusConnection *conn,
DBusMessage *msg, void *data)
{
const char *path;
DBusMessageIter iter, array;
dbus_int32_t num_changes;
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 TRUE;
}
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 TRUE;
}
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);
}
return TRUE;
}
static void add_watch(const char *sender, const char *path,
const char *interface, const char *member,
GDBusSignalFunction function)
{
guint watch;
watch = g_dbus_add_signal_watch(connection, sender, path, interface,
member, function, NULL, NULL);
watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch));
}
static void hal_find_device_reply(DBusPendingCall *call, void *user_data)
{
DBusMessage *reply;
DBusError err;
DBusMessageIter iter, sub;
int type;
const char *path;
DBG("begin of hal_find_device_reply()");
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 hal_find_device_reply()");
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-ofono: found battery device at %s", path);
add_watch(NULL, path, "org.freedesktop.Hal.Device",
"PropertyModified", handle_hal_property_modified);
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);
}
static void handle_service_connect(DBusConnection *conn, void *user_data)
{
DBG("telephony-ofono: %s found", OFONO_BUS_NAME);
send_method_call(OFONO_BUS_NAME, OFONO_PATH,
OFONO_MANAGER_INTERFACE, "GetModems",
get_modems_reply, NULL, DBUS_TYPE_INVALID);
}
static void handle_service_disconnect(DBusConnection *conn, void *user_data)
{
DBG("telephony-ofono: %s exitted", OFONO_BUS_NAME);
if (modem_obj_path)
modem_removed(modem_obj_path);
}
int telephony_init(void)
{
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;
const char *battery_cap = "battery";
int ret;
guint watch;
connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
add_watch(OFONO_BUS_NAME, NULL, OFONO_MODEM_INTERFACE,
"PropertyChanged", handle_modem_property_changed);
add_watch(OFONO_BUS_NAME, NULL, OFONO_NETWORKREG_INTERFACE,
"PropertyChanged", handle_network_property_changed);
add_watch(OFONO_BUS_NAME, NULL, OFONO_MANAGER_INTERFACE,
"ModemAdded", handle_manager_modem_added);
add_watch(OFONO_BUS_NAME, NULL, OFONO_MANAGER_INTERFACE,
"ModemRemoved", handle_manager_modem_removed);
add_watch(OFONO_BUS_NAME, NULL, OFONO_VCMANAGER_INTERFACE,
"CallAdded", handle_vcmanager_call_added);
add_watch(OFONO_BUS_NAME, NULL, OFONO_VCMANAGER_INTERFACE,
"CallRemoved", handle_vcmanager_call_removed);
watch = g_dbus_add_service_watch(connection, OFONO_BUS_NAME,
handle_service_connect,
handle_service_disconnect,
NULL, NULL);
if (watch == 0)
return -ENOMEM;
watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch));
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)
return ret;
DBG("telephony_init() successfully");
telephony_ready_ind(features, ofono_indicators, BTRH_NOT_SUPPORTED,
chld_str);
return ret;
}
static void remove_watch(gpointer data)
{
g_dbus_remove_watch(connection, GPOINTER_TO_UINT(data));
}
static void pending_free(void *data)
{
DBusPendingCall *call = data;
if (!dbus_pending_call_get_completed(call))
dbus_pending_call_cancel(call);
dbus_pending_call_unref(call);
}
void telephony_exit(void)
{
DBG("");
g_free(last_dialed_number);
last_dialed_number = NULL;
if (modem_obj_path)
modem_removed(modem_obj_path);
g_slist_free_full(watches, remove_watch);
watches = NULL;
g_slist_free_full(pending, pending_free);
pending = NULL;
dbus_connection_unref(connection);
connection = NULL;
telephony_deinit();
}