mirror of git://git.sysmocom.de/ofono
649 lines
15 KiB
C
649 lines
15 KiB
C
/*
|
|
*
|
|
* oFono - Open Source Telephony
|
|
*
|
|
* Copyright (C) 2008-2009 Intel Corporation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#include <dbus/dbus.h>
|
|
#include <glib.h>
|
|
#include <gdbus.h>
|
|
|
|
#include "ofono.h"
|
|
|
|
#include "driver.h"
|
|
#include "common.h"
|
|
#include "dbus-gsm.h"
|
|
#include "modem.h"
|
|
#include "ussd.h"
|
|
|
|
#define CALL_WAITING_INTERFACE "org.ofono.CallWaiting"
|
|
|
|
#define CALL_WAITING_FLAG_CACHED 0x1
|
|
|
|
struct call_waiting_data {
|
|
struct ofono_call_waiting_ops *ops;
|
|
int flags;
|
|
DBusMessage *pending;
|
|
GSList *cw_list;
|
|
int ss_req_type;
|
|
int ss_req_cls;
|
|
};
|
|
|
|
static const char *enabled = "enabled";
|
|
static const char *disabled = "disabled";
|
|
|
|
static void cw_register_ss_controls(struct ofono_modem *modem);
|
|
static void cw_unregister_ss_controls(struct ofono_modem *modem);
|
|
|
|
static gint cw_condition_compare(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct ofono_cw_condition *ca = a;
|
|
const struct ofono_cw_condition *cb = b;
|
|
|
|
if (ca->cls < cb->cls)
|
|
return -1;
|
|
|
|
if (ca->cls > cb->cls)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gint cw_condition_find_with_cls(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct ofono_cw_condition *c = a;
|
|
int cls = GPOINTER_TO_INT(b);
|
|
|
|
if (c->cls < cls)
|
|
return -1;
|
|
|
|
if (c->cls > cls)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct call_waiting_data *call_waiting_create()
|
|
{
|
|
struct call_waiting_data *r;
|
|
|
|
r = g_try_new0(struct call_waiting_data, 1);
|
|
|
|
if (!r)
|
|
return r;
|
|
|
|
return r;
|
|
}
|
|
|
|
static void call_waiting_destroy(gpointer data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
|
|
cw_unregister_ss_controls(modem);
|
|
|
|
g_slist_foreach(cw->cw_list, (GFunc)g_free, NULL);
|
|
g_slist_free(cw->cw_list);
|
|
|
|
g_free(cw);
|
|
}
|
|
|
|
static void cw_cond_list_print(GSList *list)
|
|
{
|
|
GSList *l;
|
|
struct ofono_cw_condition *cond;
|
|
|
|
for (l = list; l; l = l->next) {
|
|
cond = l->data;
|
|
|
|
ofono_debug("CW condition status: %d, class: %d",
|
|
cond->status, cond->cls);
|
|
}
|
|
}
|
|
|
|
static GSList *cw_cond_list_create(int total,
|
|
const struct ofono_cw_condition *list)
|
|
{
|
|
GSList *l = NULL;
|
|
int i;
|
|
int j;
|
|
struct ofono_cw_condition *cond;
|
|
|
|
/* Specification is not really clear on how the results are reported,
|
|
* most modems report it as multiple list items, one for each class
|
|
* however, specification does leave room for a single compound value
|
|
* to be reported
|
|
*/
|
|
for (i = 0; i < total; i++) {
|
|
for (j = 1; j <= BEARER_CLASS_PAD; j = j << 1) {
|
|
if (!(list[i].cls & j))
|
|
continue;
|
|
|
|
if (list[i].status == 0)
|
|
continue;
|
|
|
|
cond = g_new0(struct ofono_cw_condition, 1);
|
|
|
|
memcpy(cond, &list[i], sizeof(struct ofono_cw_condition));
|
|
cond->cls = j;
|
|
|
|
l = g_slist_insert_sorted(l, cond,
|
|
cw_condition_compare);
|
|
}
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
static void set_new_cond_list(struct ofono_modem *modem, GSList *new_cw_list)
|
|
{
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
GSList *n;
|
|
GSList *o;
|
|
struct ofono_cw_condition *nc;
|
|
struct ofono_cw_condition *oc;
|
|
char buf[64];
|
|
|
|
for (n = new_cw_list; n; n = n->next) {
|
|
nc = n->data;
|
|
|
|
if (nc->cls > BEARER_CLASS_FAX)
|
|
continue;
|
|
|
|
sprintf(buf, "%s", bearer_class_to_string(nc->cls));
|
|
|
|
o = g_slist_find_custom(cw->cw_list, GINT_TO_POINTER(nc->cls),
|
|
cw_condition_find_with_cls);
|
|
|
|
if (o) {
|
|
g_free(o->data);
|
|
cw->cw_list = g_slist_remove(cw->cw_list, o->data);
|
|
} else {
|
|
dbus_gsm_signal_property_changed(conn, modem->path,
|
|
CALL_WAITING_INTERFACE,
|
|
buf, DBUS_TYPE_STRING,
|
|
&enabled);
|
|
}
|
|
}
|
|
|
|
for (o = cw->cw_list; o; o = o->next) {
|
|
oc = o->data;
|
|
|
|
sprintf(buf, "%s", bearer_class_to_string(oc->cls));
|
|
|
|
dbus_gsm_signal_property_changed(conn, modem->path,
|
|
CALL_WAITING_INTERFACE,
|
|
buf, DBUS_TYPE_STRING,
|
|
&disabled);
|
|
}
|
|
|
|
g_slist_foreach(cw->cw_list, (GFunc)g_free, NULL);
|
|
g_slist_free(cw->cw_list);
|
|
|
|
cw->cw_list = new_cw_list;
|
|
}
|
|
|
|
static void property_append_cw_conditions(DBusMessageIter *dict,
|
|
GSList *cw_list, int mask)
|
|
{
|
|
GSList *l;
|
|
int i;
|
|
struct ofono_cw_condition *cw;
|
|
const char *prop;
|
|
|
|
for (i = 1, l = cw_list; i <= BEARER_CLASS_PAD; i = i << 1) {
|
|
if (!(mask & i))
|
|
continue;
|
|
|
|
prop = bearer_class_to_string(i);
|
|
|
|
while (l && (cw = l->data) && (cw->cls < i))
|
|
l = l->next;
|
|
|
|
if (!l || cw->cls != i) {
|
|
dbus_gsm_dict_append(dict, prop, DBUS_TYPE_STRING,
|
|
&disabled);
|
|
continue;
|
|
}
|
|
|
|
dbus_gsm_dict_append(dict, prop, DBUS_TYPE_STRING, &enabled);
|
|
}
|
|
}
|
|
|
|
static void generate_ss_query_reply(struct ofono_modem *modem)
|
|
{
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
const char *sig = "(sa{sv})";
|
|
const char *ss_type = ss_control_type_to_string(cw->ss_req_type);
|
|
const char *context = "CallWaiting";
|
|
DBusMessageIter iter;
|
|
DBusMessageIter var;
|
|
DBusMessageIter vstruct;
|
|
DBusMessageIter dict;
|
|
DBusMessage *reply;
|
|
|
|
reply = dbus_message_new_method_return(cw->pending);
|
|
|
|
dbus_message_iter_init_append(reply, &iter);
|
|
|
|
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &context);
|
|
|
|
dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, sig, &var);
|
|
|
|
dbus_message_iter_open_container(&var, DBUS_TYPE_STRUCT, NULL,
|
|
&vstruct);
|
|
|
|
dbus_message_iter_append_basic(&vstruct, DBUS_TYPE_STRING,
|
|
&ss_type);
|
|
|
|
dbus_message_iter_open_container(&vstruct, DBUS_TYPE_ARRAY,
|
|
PROPERTIES_ARRAY_SIGNATURE, &dict);
|
|
|
|
property_append_cw_conditions(&dict, cw->cw_list, cw->ss_req_cls);
|
|
|
|
dbus_message_iter_close_container(&vstruct, &dict);
|
|
|
|
dbus_message_iter_close_container(&var, &vstruct);
|
|
|
|
dbus_message_iter_close_container(&iter, &var);
|
|
|
|
dbus_gsm_pending_reply(&cw->pending, reply);
|
|
}
|
|
|
|
static void cw_ss_query_callback(const struct ofono_error *error, int num,
|
|
struct ofono_cw_condition *cond_list,
|
|
void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
GSList *l;
|
|
|
|
if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
|
|
ofono_debug("setting CW via SS failed");
|
|
|
|
cw->flags &= ~CALL_WAITING_FLAG_CACHED;
|
|
dbus_gsm_pending_reply(&cw->pending,
|
|
dbus_gsm_failed(cw->pending));
|
|
|
|
return;
|
|
}
|
|
|
|
l = cw_cond_list_create(num, cond_list);
|
|
|
|
cw_cond_list_print(l);
|
|
|
|
set_new_cond_list(modem, l);
|
|
cw->flags |= CALL_WAITING_FLAG_CACHED;
|
|
|
|
generate_ss_query_reply(modem);
|
|
}
|
|
|
|
static void cw_ss_set_callback(const struct ofono_error *error, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
|
|
if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
|
|
ofono_debug("setting CW via SS failed");
|
|
dbus_gsm_pending_reply(&cw->pending,
|
|
dbus_gsm_failed(cw->pending));
|
|
|
|
return;
|
|
}
|
|
|
|
cw->ops->query(modem, cw->ss_req_cls, cw_ss_query_callback, modem);
|
|
}
|
|
|
|
static gboolean cw_ss_control(struct ofono_modem *modem, int type,
|
|
const char *sc, const char *sia,
|
|
const char *sib, const char *sic,
|
|
const char *dn, DBusMessage *msg)
|
|
{
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
int cls = BEARER_CLASS_DEFAULT;
|
|
DBusMessage *reply;
|
|
//void *op;
|
|
|
|
if (!cw)
|
|
return FALSE;
|
|
|
|
if (strcmp(sc, "43"))
|
|
return FALSE;
|
|
|
|
if (cw->pending) {
|
|
reply = dbus_gsm_busy(msg);
|
|
goto error;
|
|
}
|
|
|
|
if (strlen(sib) || strlen(sib) || strlen(dn))
|
|
goto bad_format;
|
|
|
|
if ((type == SS_CONTROL_TYPE_QUERY && !cw->ops->query) ||
|
|
(type != SS_CONTROL_TYPE_QUERY && !cw->ops->set)) {
|
|
reply = dbus_gsm_not_implemented(msg);
|
|
goto error;
|
|
}
|
|
|
|
if (strlen(sia) > 0) {
|
|
long service_code;
|
|
char *end;
|
|
|
|
service_code = strtoul(sia, &end, 10);
|
|
|
|
if (end == sia || *end != '\0')
|
|
goto bad_format;
|
|
|
|
cls = mmi_service_code_to_bearer_class(service_code);
|
|
if (cls == 0)
|
|
goto bad_format;
|
|
}
|
|
|
|
cw->ss_req_cls = cls;
|
|
cw->pending = dbus_message_ref(msg);
|
|
|
|
switch (type) {
|
|
case SS_CONTROL_TYPE_REGISTRATION:
|
|
case SS_CONTROL_TYPE_ACTIVATION:
|
|
cw->ss_req_type = SS_CONTROL_TYPE_ACTIVATION;
|
|
cw->ops->set(modem, 1, cls, cw_ss_set_callback, modem);
|
|
break;
|
|
|
|
case SS_CONTROL_TYPE_QUERY:
|
|
cw->ss_req_type = SS_CONTROL_TYPE_QUERY;
|
|
cw->ops->query(modem, cls, cw_ss_query_callback, modem);
|
|
break;
|
|
|
|
case SS_CONTROL_TYPE_DEACTIVATION:
|
|
case SS_CONTROL_TYPE_ERASURE:
|
|
cw->ss_req_type = SS_CONTROL_TYPE_DEACTIVATION;
|
|
cw->ops->set(modem, 0, cls, cw_ss_set_callback, modem);
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
bad_format:
|
|
reply = dbus_gsm_invalid_format(msg);
|
|
error:
|
|
g_dbus_send_message(conn, reply);
|
|
return TRUE;
|
|
}
|
|
|
|
static void cw_register_ss_controls(struct ofono_modem *modem)
|
|
{
|
|
ss_control_register(modem, "43", cw_ss_control);
|
|
}
|
|
|
|
static void cw_unregister_ss_controls(struct ofono_modem *modem)
|
|
{
|
|
ss_control_unregister(modem, "43", cw_ss_control);
|
|
}
|
|
|
|
static DBusMessage *generate_get_properties_reply(struct ofono_modem *modem,
|
|
DBusMessage *msg)
|
|
{
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
DBusMessage *reply;
|
|
DBusMessageIter iter;
|
|
DBusMessageIter dict;
|
|
//int i;
|
|
//GSList *l;
|
|
|
|
reply = dbus_message_new_method_return(msg);
|
|
|
|
if (!reply)
|
|
return NULL;
|
|
|
|
dbus_message_iter_init_append(reply, &iter);
|
|
|
|
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
|
|
PROPERTIES_ARRAY_SIGNATURE,
|
|
&dict);
|
|
|
|
property_append_cw_conditions(&dict, cw->cw_list, BEARER_CLASS_DEFAULT);
|
|
|
|
dbus_message_iter_close_container(&iter, &dict);
|
|
|
|
return reply;
|
|
}
|
|
|
|
static void cw_query_callback(const struct ofono_error *error, int num,
|
|
struct ofono_cw_condition *cond_list, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
GSList *l = NULL;
|
|
|
|
if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
|
|
ofono_debug("Error during cw query");
|
|
goto out;
|
|
}
|
|
|
|
l = cw_cond_list_create(num, cond_list);
|
|
|
|
cw_cond_list_print(l);
|
|
|
|
set_new_cond_list(modem, l);
|
|
cw->flags |= CALL_WAITING_FLAG_CACHED;
|
|
|
|
out:
|
|
if (cw->pending) {
|
|
DBusMessage *reply;
|
|
|
|
reply = generate_get_properties_reply(modem, cw->pending);
|
|
dbus_gsm_pending_reply(&cw->pending, reply);
|
|
}
|
|
}
|
|
|
|
static DBusMessage *cw_get_properties(DBusConnection *conn, DBusMessage *msg,
|
|
void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
|
|
if (cw->pending)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
if (!cw->ops->query)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
if (cw->flags & CALL_WAITING_FLAG_CACHED)
|
|
return generate_get_properties_reply(modem, msg);
|
|
|
|
cw->pending = dbus_message_ref(msg);
|
|
|
|
cw->ops->query(modem, BEARER_CLASS_DEFAULT, cw_query_callback, modem);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void set_query_callback(const struct ofono_error *error, int num,
|
|
struct ofono_cw_condition *cond_list, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
GSList *l = NULL;
|
|
|
|
if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
|
|
ofono_error("CW set succeeded, but query failed!");
|
|
cw->flags &= ~CALL_WAITING_FLAG_CACHED;
|
|
|
|
dbus_gsm_pending_reply(&cw->pending,
|
|
dbus_gsm_failed(cw->pending));
|
|
return;
|
|
}
|
|
|
|
dbus_gsm_pending_reply(&cw->pending,
|
|
dbus_message_new_method_return(cw->pending));
|
|
|
|
l = cw_cond_list_create(num, cond_list);
|
|
|
|
cw_cond_list_print(l);
|
|
|
|
set_new_cond_list(modem, l);
|
|
}
|
|
|
|
static void set_callback(const struct ofono_error *error, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
|
|
if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
|
|
ofono_debug("Error occurred during CW set");
|
|
|
|
dbus_gsm_pending_reply(&cw->pending,
|
|
dbus_gsm_failed(cw->pending));
|
|
|
|
return;
|
|
}
|
|
|
|
cw->ops->query(modem, BEARER_CLASS_DEFAULT, set_query_callback, modem);
|
|
}
|
|
|
|
static DBusMessage *cw_set_property(DBusConnection *conn, DBusMessage *msg,
|
|
void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
DBusMessageIter iter;
|
|
DBusMessageIter var;
|
|
const char *property;
|
|
int i;
|
|
|
|
if (cw->pending)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
if (!cw->ops->set)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
if (!dbus_message_iter_init(msg, &iter))
|
|
return dbus_gsm_invalid_args(msg);
|
|
|
|
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
|
|
return dbus_gsm_invalid_args(msg);
|
|
|
|
dbus_message_iter_get_basic(&iter, &property);
|
|
dbus_message_iter_next(&iter);
|
|
|
|
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
|
|
return dbus_gsm_invalid_args(msg);
|
|
|
|
dbus_message_iter_recurse(&iter, &var);
|
|
|
|
for (i = 1; i < BEARER_CLASS_SMS; i = i << 1)
|
|
if (!strcmp(property, bearer_class_to_string(i)))
|
|
break;
|
|
|
|
if (i < BEARER_CLASS_SMS) {
|
|
const char *value;
|
|
int status;
|
|
|
|
if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING)
|
|
return dbus_gsm_invalid_format(msg);
|
|
|
|
dbus_message_iter_get_basic(&var, &value);
|
|
|
|
if (!strcmp(value, "enabled"))
|
|
status = 1;
|
|
else if (!strcmp(value, "disabled"))
|
|
status = 0;
|
|
else
|
|
return dbus_gsm_invalid_format(msg);
|
|
|
|
cw->pending = dbus_message_ref(msg);
|
|
|
|
cw->ops->set(modem, status, i, set_callback, modem);
|
|
}
|
|
|
|
return dbus_gsm_invalid_args(msg);
|
|
}
|
|
|
|
static GDBusMethodTable cw_methods[] = {
|
|
{ "GetProperties", "", "a{sv}", cw_get_properties,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "SetProperty", "sv", "", cw_set_property,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ }
|
|
};
|
|
|
|
static GDBusSignalTable cw_signals[] = {
|
|
{ "PropertyChanged", "sv" },
|
|
{ }
|
|
};
|
|
|
|
int ofono_call_waiting_register(struct ofono_modem *modem,
|
|
struct ofono_call_waiting_ops *ops)
|
|
{
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
|
|
if (modem == NULL)
|
|
return -1;
|
|
|
|
if (ops == NULL)
|
|
return -1;
|
|
|
|
modem->call_waiting = call_waiting_create();
|
|
|
|
if (!modem->call_waiting)
|
|
return -1;
|
|
|
|
modem->call_waiting->ops = ops;
|
|
|
|
if (!g_dbus_register_interface(conn, modem->path,
|
|
CALL_WAITING_INTERFACE,
|
|
cw_methods, cw_signals, NULL,
|
|
modem, call_waiting_destroy)) {
|
|
ofono_error("Could not register CallWaiting %s", modem->path);
|
|
call_waiting_destroy(modem);
|
|
|
|
return -1;
|
|
}
|
|
|
|
ofono_debug("Registered call waiting interface");
|
|
|
|
cw_register_ss_controls(modem);
|
|
|
|
modem_add_interface(modem, CALL_WAITING_INTERFACE);
|
|
return 0;
|
|
}
|
|
|
|
void ofono_call_waiting_unregister(struct ofono_modem *modem)
|
|
{
|
|
struct call_waiting_data *cw = modem->call_waiting;
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
|
|
if (!cw)
|
|
return;
|
|
|
|
modem_remove_interface(modem, CALL_WAITING_INTERFACE);
|
|
g_dbus_unregister_interface(conn, modem->path,
|
|
CALL_WAITING_INTERFACE);
|
|
|
|
modem->call_waiting = NULL;
|
|
}
|