mirror of git://git.sysmocom.de/ofono
1673 lines
40 KiB
C
1673 lines
40 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 <stdio.h>
|
|
#include <time.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 "cssn.h"
|
|
|
|
#define VOICECALL_MANAGER_INTERFACE "org.ofono.VoiceCallManager"
|
|
#define VOICECALL_INTERFACE "org.ofono.VoiceCall"
|
|
|
|
#define VOICECALLS_FLAG_PENDING 0x1
|
|
#define VOICECALLS_FLAG_MULTI_RELEASE 0x2
|
|
#define VOICECALLS_FLAG_UPDATING_CALL_LIST 0x4
|
|
#define VOICECALLS_FLAG_UPDATING_MPTY_CALL_LIST 0x8
|
|
|
|
#define MAX_VOICE_CALLS 16
|
|
|
|
struct voicecalls_data {
|
|
GSList *call_list;
|
|
GSList *release_list;
|
|
GSList *multiparty_list;
|
|
struct ofono_voicecall_ops *ops;
|
|
int flags;
|
|
DBusMessage *pending;
|
|
};
|
|
|
|
struct voicecall {
|
|
struct ofono_call *call;
|
|
struct ofono_modem *modem;
|
|
time_t start_time;
|
|
};
|
|
|
|
static void generic_callback(const struct ofono_error *error, void *data);
|
|
static void dial_callback(const struct ofono_error *error, void *data);
|
|
static void multirelease_callback(const struct ofono_error *err, void *data);
|
|
static void multiparty_create_callback(const struct ofono_error *error,
|
|
void *data);
|
|
static void private_chat_callback(const struct ofono_error *error, void *data);
|
|
|
|
static gint call_compare_by_id(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct ofono_call *call = ((struct voicecall *)a)->call;
|
|
unsigned int id = GPOINTER_TO_UINT(b);
|
|
|
|
if (id < call->id)
|
|
return -1;
|
|
|
|
if (id > call->id)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gint call_compare(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct voicecall *ca = a;
|
|
const struct voicecall *cb = b;
|
|
|
|
if (ca->call->id < cb->call->id)
|
|
return -1;
|
|
|
|
if (ca->call->id > cb->call->id)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char *call_status_to_string(int status)
|
|
{
|
|
switch (status) {
|
|
case CALL_STATUS_ACTIVE:
|
|
return "active";
|
|
case CALL_STATUS_HELD:
|
|
return "held";
|
|
case CALL_STATUS_DIALING:
|
|
return "dialing";
|
|
case CALL_STATUS_ALERTING:
|
|
return "alerting";
|
|
case CALL_STATUS_INCOMING:
|
|
return "incoming";
|
|
case CALL_STATUS_WAITING:
|
|
return "waiting";
|
|
default:
|
|
return "disconnected";
|
|
}
|
|
}
|
|
|
|
static const char *phone_and_clip_to_string(const char *number, int type,
|
|
int clip_validity)
|
|
{
|
|
if (clip_validity == CLIP_VALIDITY_WITHHELD && !strlen(number))
|
|
return "withheld";
|
|
|
|
if (clip_validity == CLIP_VALIDITY_NOT_AVAILABLE)
|
|
return "";
|
|
|
|
return phone_number_to_string(number, type);
|
|
}
|
|
|
|
static const char *time_to_str(const time_t *t)
|
|
{
|
|
static char buf[128];
|
|
|
|
strftime(buf, 127, "%a, %d %b %Y %H:%M:%S %z", localtime(t));
|
|
buf[127] = '\0';
|
|
|
|
return buf;
|
|
}
|
|
|
|
static DBusMessage *voicecall_get_properties(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct voicecall *v = data;
|
|
struct ofono_call *call = v->call;
|
|
DBusMessage *reply;
|
|
DBusMessageIter iter;
|
|
DBusMessageIter dict;
|
|
const char *status;
|
|
const char *callerid;
|
|
const char *timestr = "";
|
|
|
|
reply = dbus_message_new_method_return(msg);
|
|
|
|
if (!reply)
|
|
return NULL;
|
|
|
|
status = call_status_to_string(call->status);
|
|
callerid = phone_number_to_string(call->phone_number,
|
|
call->number_type);
|
|
|
|
dbus_message_iter_init_append(reply, &iter);
|
|
|
|
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
|
|
PROPERTIES_ARRAY_SIGNATURE,
|
|
&dict);
|
|
|
|
dbus_gsm_dict_append(&dict, "State", DBUS_TYPE_STRING, &status);
|
|
|
|
dbus_gsm_dict_append(&dict, "LineIdentification",
|
|
DBUS_TYPE_STRING, &callerid);
|
|
|
|
if (call->status == CALL_STATUS_ACTIVE ||
|
|
(call->status == CALL_STATUS_DISCONNECTED && v->start_time != 0) ||
|
|
call->status == CALL_STATUS_HELD) {
|
|
timestr = time_to_str(&v->start_time);
|
|
|
|
dbus_gsm_dict_append(&dict, "StartTime", DBUS_TYPE_STRING,
|
|
×tr);
|
|
}
|
|
|
|
dbus_message_iter_close_container(&iter, &dict);
|
|
|
|
return reply;
|
|
}
|
|
|
|
static DBusMessage *voicecall_busy(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct voicecall *v = data;
|
|
struct ofono_modem *modem = v->modem;
|
|
struct voicecalls_data *voicecalls = modem->voicecalls;
|
|
struct ofono_call *call = v->call;
|
|
|
|
if (call->status != CALL_STATUS_INCOMING &&
|
|
call->status != CALL_STATUS_WAITING)
|
|
return dbus_gsm_failed(msg);
|
|
|
|
if (!voicecalls->ops->release_specific)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
if (voicecalls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
voicecalls->flags |= VOICECALLS_FLAG_PENDING;
|
|
voicecalls->pending = dbus_message_ref(msg);
|
|
|
|
voicecalls->ops->set_udub(modem, generic_callback, voicecalls);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusMessage *voicecall_deflect(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct voicecall *v = data;
|
|
struct ofono_modem *modem = v->modem;
|
|
struct voicecalls_data *voicecalls = modem->voicecalls;
|
|
struct ofono_call *call = v->call;
|
|
|
|
const char *number;
|
|
int number_type;
|
|
|
|
if (call->status != CALL_STATUS_INCOMING &&
|
|
call->status != CALL_STATUS_WAITING)
|
|
return dbus_gsm_failed(msg);
|
|
|
|
if (!voicecalls->ops->deflect)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
if (voicecalls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
|
|
DBUS_TYPE_INVALID) == FALSE)
|
|
return dbus_gsm_invalid_args(msg);
|
|
|
|
if (!valid_phone_number_format(number))
|
|
return dbus_gsm_invalid_format(msg);
|
|
|
|
voicecalls->flags |= VOICECALLS_FLAG_PENDING;
|
|
voicecalls->pending = dbus_message_ref(msg);
|
|
|
|
string_to_phone_number(number, &number_type, &number);
|
|
|
|
voicecalls->ops->deflect(modem, number, number_type,
|
|
generic_callback, voicecalls);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusMessage *voicecall_hangup(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct voicecall *v = data;
|
|
struct ofono_modem *modem = v->modem;
|
|
struct voicecalls_data *voicecalls = modem->voicecalls;
|
|
struct ofono_call *call = v->call;
|
|
|
|
if (call->status == CALL_STATUS_DISCONNECTED)
|
|
return dbus_gsm_failed(msg);
|
|
|
|
if (!voicecalls->ops->release_specific)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
if (voicecalls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
voicecalls->flags |= VOICECALLS_FLAG_PENDING;
|
|
voicecalls->pending = dbus_message_ref(msg);
|
|
|
|
voicecalls->ops->release_specific(modem, call->id,
|
|
generic_callback, voicecalls);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusMessage *voicecall_answer(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct voicecall *v = data;
|
|
struct ofono_modem *modem = v->modem;
|
|
struct voicecalls_data *voicecalls = modem->voicecalls;
|
|
struct ofono_call *call = v->call;
|
|
|
|
if (call->status != CALL_STATUS_INCOMING)
|
|
return dbus_gsm_failed(msg);
|
|
|
|
if (!voicecalls->ops->answer)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
if (voicecalls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
voicecalls->flags |= VOICECALLS_FLAG_PENDING;
|
|
voicecalls->pending = dbus_message_ref(msg);
|
|
|
|
voicecalls->ops->answer(modem, generic_callback, voicecalls);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static GDBusMethodTable voicecall_methods[] = {
|
|
{ "GetProperties", "", "a{sv}", voicecall_get_properties },
|
|
{ "Busy", "", "", voicecall_busy,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "Deflect", "s", "", voicecall_deflect,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "Hangup", "", "", voicecall_hangup,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "Answer", "", "", voicecall_answer,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ }
|
|
};
|
|
|
|
static GDBusSignalTable voicecall_signals[] = {
|
|
{ "PropertyChanged", "sv" },
|
|
{ "DisconnectReason", "s" },
|
|
{ }
|
|
};
|
|
|
|
static struct voicecall *voicecall_create(struct ofono_modem *modem,
|
|
struct ofono_call *call)
|
|
{
|
|
struct voicecall *v;
|
|
|
|
v = g_try_new0(struct voicecall, 1);
|
|
|
|
if (!v)
|
|
return NULL;
|
|
|
|
v->call = call;
|
|
v->modem = modem;
|
|
|
|
return v;
|
|
}
|
|
|
|
static void voicecall_destroy(gpointer userdata)
|
|
{
|
|
struct voicecall *voicecall = (struct voicecall *)userdata;
|
|
|
|
g_free(voicecall->call);
|
|
|
|
g_free(voicecall);
|
|
}
|
|
|
|
static const char *voicecall_build_path(struct ofono_modem *modem,
|
|
const struct ofono_call *call)
|
|
{
|
|
static char path[MAX_DBUS_PATH_LEN];
|
|
|
|
snprintf(path, MAX_DBUS_PATH_LEN, "%s/voicecall%02d",
|
|
modem->path, call->id);
|
|
|
|
return path;
|
|
}
|
|
|
|
static void voicecall_set_call_status(struct ofono_modem *modem,
|
|
struct voicecall *call,
|
|
int status)
|
|
{
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
const char *path;
|
|
const char *status_str;
|
|
int old_status;
|
|
|
|
if (call->call->status == status)
|
|
return;
|
|
|
|
old_status = call->call->status;
|
|
|
|
call->call->status = status;
|
|
|
|
status_str = call_status_to_string(status);
|
|
path = voicecall_build_path(modem, call->call);
|
|
|
|
dbus_gsm_signal_property_changed(conn, path, VOICECALL_INTERFACE,
|
|
"State", DBUS_TYPE_STRING,
|
|
&status_str);
|
|
|
|
if (status == CALL_STATUS_ACTIVE &&
|
|
(old_status == CALL_STATUS_INCOMING ||
|
|
old_status == CALL_STATUS_DIALING ||
|
|
old_status == CALL_STATUS_ALERTING ||
|
|
old_status == CALL_STATUS_WAITING)) {
|
|
const char *timestr;
|
|
|
|
call->start_time = time(NULL);
|
|
timestr = time_to_str(&call->start_time);
|
|
|
|
dbus_gsm_signal_property_changed(conn, path,
|
|
VOICECALL_INTERFACE,
|
|
"StartTime",
|
|
DBUS_TYPE_STRING,
|
|
×tr);
|
|
}
|
|
}
|
|
|
|
static void voicecall_set_call_lineid(struct ofono_modem *modem,
|
|
struct voicecall *v,
|
|
const char *number, int number_type,
|
|
int clip_validity)
|
|
{
|
|
struct ofono_call *call = v->call;
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
const char *path;
|
|
const char *lineid_str;
|
|
|
|
if (!strcmp(call->phone_number, number) &&
|
|
call->number_type == number_type &&
|
|
call->clip_validity == clip_validity)
|
|
return;
|
|
|
|
/* Two cases: We get an incoming call with CLIP factored in, or
|
|
* CLIP comes in later as a separate event
|
|
* For COLP only the phone number should be checked, it can come
|
|
* in with the initial call event or later as a separate event */
|
|
|
|
/* For plugins that don't keep state, ignore */
|
|
if (call->clip_validity == CLIP_VALIDITY_VALID &&
|
|
clip_validity == CLIP_VALIDITY_NOT_AVAILABLE)
|
|
return;
|
|
|
|
strcpy(call->phone_number, number);
|
|
call->clip_validity = clip_validity;
|
|
call->number_type = number_type;
|
|
|
|
path = voicecall_build_path(modem, call);
|
|
|
|
if (call->direction == CALL_DIRECTION_MOBILE_TERMINATED)
|
|
lineid_str = phone_and_clip_to_string(number, number_type,
|
|
clip_validity);
|
|
else
|
|
lineid_str = phone_number_to_string(number, number_type);
|
|
|
|
dbus_gsm_signal_property_changed(conn, path, VOICECALL_INTERFACE,
|
|
"LineIdentification",
|
|
DBUS_TYPE_STRING, &lineid_str);
|
|
}
|
|
|
|
static gboolean voicecall_dbus_register(struct voicecall *voicecall)
|
|
{
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
const char *path;
|
|
|
|
if (!voicecall)
|
|
return FALSE;
|
|
|
|
path = voicecall_build_path(voicecall->modem, voicecall->call);
|
|
|
|
if (!g_dbus_register_interface(conn, path, VOICECALL_INTERFACE,
|
|
voicecall_methods,
|
|
voicecall_signals,
|
|
NULL, voicecall,
|
|
voicecall_destroy)) {
|
|
ofono_error("Could not register VoiceCall %s", path);
|
|
voicecall_destroy(voicecall);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean voicecall_dbus_unregister(struct ofono_modem *modem,
|
|
struct voicecall *call)
|
|
{
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
const char *path = voicecall_build_path(modem, call->call);
|
|
|
|
return g_dbus_unregister_interface(conn, path,
|
|
VOICECALL_INTERFACE);
|
|
}
|
|
|
|
static struct voicecalls_data *voicecalls_create()
|
|
{
|
|
struct voicecalls_data *calls;
|
|
|
|
calls = g_try_new0(struct voicecalls_data, 1);
|
|
|
|
return calls;
|
|
}
|
|
|
|
static void voicecalls_destroy(gpointer userdata)
|
|
{
|
|
struct ofono_modem *modem = userdata;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
GSList *l;
|
|
|
|
for (l = calls->call_list; l; l = l->next)
|
|
voicecall_dbus_unregister(modem, l->data);
|
|
|
|
g_slist_free(calls->call_list);
|
|
|
|
g_free(calls);
|
|
|
|
modem->voicecalls = 0;
|
|
}
|
|
|
|
static int voicecalls_path_list(struct ofono_modem *modem, GSList *call_list,
|
|
char ***objlist)
|
|
{
|
|
GSList *l;
|
|
int i;
|
|
struct voicecall *v;
|
|
|
|
*objlist = g_new0(char *, g_slist_length(call_list) + 1);
|
|
|
|
if (*objlist == NULL)
|
|
return -1;
|
|
|
|
for (i = 0, l = call_list; l; l = l->next, i++) {
|
|
v = l->data;
|
|
(*objlist)[i] = g_strdup(voicecall_build_path(modem, v->call));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean voicecalls_have_active(struct voicecalls_data *calls)
|
|
{
|
|
GSList *l;
|
|
struct voicecall *v;
|
|
|
|
for (l = calls->call_list; l; l = l->next) {
|
|
v = l->data;
|
|
|
|
if (v->call->status == CALL_STATUS_ACTIVE ||
|
|
v->call->status == CALL_STATUS_INCOMING ||
|
|
v->call->status == CALL_STATUS_DIALING ||
|
|
v->call->status == CALL_STATUS_ALERTING)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean voicecalls_have_connected(struct voicecalls_data *calls)
|
|
{
|
|
GSList *l;
|
|
struct voicecall *v;
|
|
|
|
for (l = calls->call_list; l; l = l->next) {
|
|
v = l->data;
|
|
|
|
if (v->call->status == CALL_STATUS_ACTIVE)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean voicecalls_have_held(struct voicecalls_data *calls)
|
|
{
|
|
GSList *l;
|
|
struct voicecall *v;
|
|
|
|
for (l = calls->call_list; l; l = l->next) {
|
|
v = l->data;
|
|
|
|
if (v->call->status == CALL_STATUS_HELD)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static int voicecalls_num_with_status(struct voicecalls_data *calls,
|
|
int status)
|
|
{
|
|
GSList *l;
|
|
struct voicecall *v;
|
|
int num = 0;
|
|
|
|
for (l = calls->call_list; l; l = l->next) {
|
|
v = l->data;
|
|
|
|
if (v->call->status == status)
|
|
num += 1;
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
static int voicecalls_num_active(struct voicecalls_data *calls)
|
|
{
|
|
return voicecalls_num_with_status(calls, CALL_STATUS_ACTIVE);
|
|
}
|
|
|
|
static int voicecalls_num_held(struct voicecalls_data *calls)
|
|
{
|
|
return voicecalls_num_with_status(calls, CALL_STATUS_HELD);
|
|
}
|
|
|
|
static int voicecalls_num_connecting(struct voicecalls_data *calls)
|
|
{
|
|
int r = 0;
|
|
|
|
r += voicecalls_num_with_status(calls, CALL_STATUS_DIALING);
|
|
r += voicecalls_num_with_status(calls, CALL_STATUS_ALERTING);
|
|
|
|
return r;
|
|
}
|
|
|
|
static GSList *voicecalls_held_list(struct voicecalls_data *calls)
|
|
{
|
|
GSList *l;
|
|
GSList *r = NULL;
|
|
struct voicecall *v;
|
|
|
|
for (l = calls->call_list; l; l = l->next) {
|
|
v = l->data;
|
|
|
|
if (v->call->status == CALL_STATUS_HELD)
|
|
r = g_slist_prepend(r, v);
|
|
}
|
|
|
|
if (r)
|
|
r = g_slist_reverse(r);
|
|
|
|
return r;
|
|
}
|
|
|
|
/* Intended to be used for multiparty, which cannot be incoming,
|
|
* alerting or dialing */
|
|
static GSList *voicecalls_active_list(struct voicecalls_data *calls)
|
|
{
|
|
GSList *l;
|
|
GSList *r = NULL;
|
|
struct voicecall *v;
|
|
|
|
for (l = calls->call_list; l; l = l->next) {
|
|
v = l->data;
|
|
|
|
if (v->call->status == CALL_STATUS_ACTIVE)
|
|
r = g_slist_prepend(r, v);
|
|
}
|
|
|
|
if (r)
|
|
r = g_slist_reverse(r);
|
|
|
|
return r;
|
|
}
|
|
|
|
static gboolean voicecalls_have_waiting(struct voicecalls_data *calls)
|
|
{
|
|
GSList *l;
|
|
struct voicecall *v;
|
|
|
|
for (l = calls->call_list; l; l = l->next) {
|
|
v = l->data;
|
|
|
|
if (v->call->status == CALL_STATUS_WAITING)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void voicecalls_release_queue(struct ofono_modem *modem, GSList *calls)
|
|
{
|
|
struct voicecalls_data *voicecalls = modem->voicecalls;
|
|
GSList *l;
|
|
|
|
g_slist_free(voicecalls->release_list);
|
|
voicecalls->release_list = NULL;
|
|
|
|
for (l = calls; l; l = l->next) {
|
|
voicecalls->release_list =
|
|
g_slist_prepend(voicecalls->release_list, l->data);
|
|
}
|
|
}
|
|
|
|
static void voicecalls_release_next(struct ofono_modem *modem)
|
|
{
|
|
struct voicecalls_data *voicecalls = modem->voicecalls;
|
|
struct voicecall *call;
|
|
|
|
if (!voicecalls->release_list)
|
|
return;
|
|
|
|
call = voicecalls->release_list->data;
|
|
|
|
voicecalls->release_list = g_slist_remove(voicecalls->release_list,
|
|
call);
|
|
|
|
voicecalls->ops->release_specific(modem, call->call->id,
|
|
multirelease_callback, modem);
|
|
}
|
|
|
|
static DBusMessage *manager_get_properties(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
DBusMessage *reply;
|
|
DBusMessageIter iter;
|
|
DBusMessageIter dict;
|
|
|
|
char **callobj_list;
|
|
|
|
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);
|
|
|
|
voicecalls_path_list(modem, calls->call_list, &callobj_list);
|
|
|
|
dbus_gsm_dict_append_array(&dict, "Calls", DBUS_TYPE_OBJECT_PATH,
|
|
&callobj_list);
|
|
|
|
dbus_gsm_free_string_array(callobj_list);
|
|
|
|
voicecalls_path_list(modem, calls->multiparty_list, &callobj_list);
|
|
|
|
dbus_gsm_dict_append_array(&dict, "MultipartyCalls",
|
|
DBUS_TYPE_OBJECT_PATH, &callobj_list);
|
|
|
|
dbus_gsm_free_string_array(callobj_list);
|
|
|
|
dbus_message_iter_close_container(&iter, &dict);
|
|
|
|
return reply;
|
|
}
|
|
|
|
static DBusMessage *manager_dial(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
const char *number;
|
|
int number_type;
|
|
const char *clirstr;
|
|
enum ofono_clir_option clir;
|
|
|
|
if (calls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
if (g_slist_length(calls->call_list) >= MAX_VOICE_CALLS)
|
|
return dbus_gsm_failed(msg);
|
|
|
|
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
|
|
DBUS_TYPE_STRING, &clirstr,
|
|
DBUS_TYPE_INVALID) == FALSE)
|
|
return dbus_gsm_invalid_args(msg);
|
|
|
|
if (!valid_phone_number_format(number))
|
|
return dbus_gsm_invalid_format(msg);
|
|
|
|
if (strlen(clirstr) == 0 || !strcmp(clirstr, "default"))
|
|
clir = OFONO_CLIR_OPTION_DEFAULT;
|
|
else if (!strcmp(clirstr, "disabled"))
|
|
clir = OFONO_CLIR_OPTION_SUPPRESSION;
|
|
else if (!strcmp(clirstr, "enabled"))
|
|
clir = OFONO_CLIR_OPTION_INVOCATION;
|
|
else
|
|
return dbus_gsm_invalid_format(msg);
|
|
|
|
if (!calls->ops->dial)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
if (voicecalls_have_active(calls) &&
|
|
voicecalls_have_held(calls))
|
|
return dbus_gsm_failed(msg);
|
|
|
|
calls->flags |= VOICECALLS_FLAG_PENDING;
|
|
calls->pending = dbus_message_ref(msg);
|
|
|
|
string_to_phone_number(number, &number_type, &number);
|
|
|
|
calls->ops->dial(modem, number, number_type, clir,
|
|
OFONO_CUG_OPTION_DEFAULT,
|
|
dial_callback, modem);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusMessage *manager_transfer(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
int numactive;
|
|
int numheld;
|
|
|
|
if (calls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
numactive = voicecalls_num_active(calls);
|
|
|
|
/* According to 22.091 section 5.8, the network has the option of
|
|
* implementing the call transfer operation for a call that is
|
|
* still dialing/alerting.
|
|
*/
|
|
numactive += voicecalls_num_connecting(calls);
|
|
|
|
numheld = voicecalls_num_held(calls);
|
|
|
|
if ((numactive != 1) && (numheld != 1))
|
|
return dbus_gsm_failed(msg);
|
|
|
|
if (!calls->ops->transfer)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
calls->flags |= VOICECALLS_FLAG_PENDING;
|
|
calls->pending = dbus_message_ref(msg);
|
|
|
|
calls->ops->transfer(modem, generic_callback, calls);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusMessage *manager_swap_calls(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
|
|
if (calls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
if (voicecalls_have_waiting(calls))
|
|
return dbus_gsm_failed(msg);
|
|
|
|
if (!calls->ops->hold_all_active)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
calls->flags |= VOICECALLS_FLAG_PENDING;
|
|
calls->pending = dbus_message_ref(msg);
|
|
|
|
calls->ops->hold_all_active(modem, generic_callback, calls);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusMessage *manager_release_and_answer(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
|
|
if (calls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
if (!voicecalls_have_active(calls) || !voicecalls_have_waiting(calls))
|
|
return dbus_gsm_failed(msg);
|
|
|
|
if (!calls->ops->release_all_active)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
calls->flags |= VOICECALLS_FLAG_PENDING;
|
|
calls->pending = dbus_message_ref(msg);
|
|
|
|
calls->ops->release_all_active(modem, generic_callback, calls);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusMessage *manager_hold_and_answer(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
|
|
if (calls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
if (voicecalls_have_active(calls) && voicecalls_have_held(calls) &&
|
|
voicecalls_have_waiting(calls))
|
|
return dbus_gsm_failed(msg);
|
|
|
|
if (!calls->ops->hold_all_active)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
calls->flags |= VOICECALLS_FLAG_PENDING;
|
|
calls->pending = dbus_message_ref(msg);
|
|
|
|
calls->ops->hold_all_active(modem, generic_callback, calls);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusMessage *manager_hangup_all(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
|
|
if (calls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
if (!calls->ops->release_specific)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
if (g_slist_length(calls->call_list) == 0) {
|
|
DBusMessage *reply = dbus_message_new_method_return(msg);
|
|
return reply;
|
|
}
|
|
|
|
calls->flags |= VOICECALLS_FLAG_PENDING;
|
|
calls->flags |= VOICECALLS_FLAG_MULTI_RELEASE;
|
|
|
|
calls->pending = dbus_message_ref(msg);
|
|
|
|
voicecalls_release_queue(modem, calls->call_list);
|
|
voicecalls_release_next(modem);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusMessage *multiparty_private_chat(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
const char *callpath;
|
|
const char *c;
|
|
unsigned int id;
|
|
GSList *l;
|
|
|
|
if (calls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &callpath,
|
|
DBUS_TYPE_INVALID) == FALSE)
|
|
return dbus_gsm_invalid_args(msg);
|
|
|
|
if (strlen(callpath) == 0 || strlen(callpath) > MAX_DBUS_PATH_LEN)
|
|
return dbus_gsm_invalid_format(msg);
|
|
|
|
c = strrchr(callpath, '/');
|
|
|
|
if (!c || strncmp(modem->path, callpath, c-callpath))
|
|
return dbus_gsm_not_found(msg);
|
|
|
|
if (!sscanf(c, "/voicecall%2u", &id))
|
|
return dbus_gsm_not_found(msg);
|
|
|
|
for (l = calls->multiparty_list; l; l = l->next) {
|
|
struct voicecall *v = l->data;
|
|
if (v->call->id == id)
|
|
break;
|
|
}
|
|
|
|
if (!l)
|
|
return dbus_gsm_not_found(msg);
|
|
|
|
/* If we found id on the list of multiparty calls, then by definition
|
|
* the multiparty call exists. Only thing to check is whether we have
|
|
* held calls
|
|
*/
|
|
if (voicecalls_have_held(calls))
|
|
return dbus_gsm_failed(msg);
|
|
|
|
if (!calls->ops->private_chat)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
calls->flags |= VOICECALLS_FLAG_PENDING;
|
|
calls->pending = dbus_message_ref(msg);
|
|
|
|
calls->ops->private_chat(modem, id, private_chat_callback, modem);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusMessage *multiparty_create(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
|
|
if (calls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
if (!voicecalls_have_held(calls) || !voicecalls_have_active(calls))
|
|
return dbus_gsm_failed(msg);
|
|
|
|
if (!calls->ops->create_multiparty)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
calls->flags |= VOICECALLS_FLAG_PENDING;
|
|
calls->pending = dbus_message_ref(msg);
|
|
|
|
calls->ops->create_multiparty(modem, multiparty_create_callback, modem);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusMessage *multiparty_hangup(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
|
|
if (calls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
if (!calls->ops->release_specific)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
if (g_slist_length(calls->multiparty_list) == 0) {
|
|
DBusMessage *reply = dbus_message_new_method_return(msg);
|
|
return reply;
|
|
}
|
|
|
|
calls->flags |= VOICECALLS_FLAG_PENDING;
|
|
calls->pending = dbus_message_ref(msg);
|
|
|
|
/* We have waiting calls, can't use +CHLD to release */
|
|
if (voicecalls_have_waiting(calls)) {
|
|
calls->flags |= VOICECALLS_FLAG_MULTI_RELEASE;
|
|
voicecalls_release_queue(modem, calls->multiparty_list);
|
|
voicecalls_release_next(modem);
|
|
} else {
|
|
struct ofono_call *v = calls->multiparty_list->data;
|
|
|
|
if (v->status == CALL_STATUS_HELD)
|
|
calls->ops->release_all_held(modem, generic_callback,
|
|
calls);
|
|
else
|
|
calls->ops->release_all_active(modem, generic_callback,
|
|
calls);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusMessage *manager_tone(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
const char *in_tones;
|
|
char *tones;
|
|
int i, len;
|
|
|
|
if (calls->flags & VOICECALLS_FLAG_PENDING)
|
|
return dbus_gsm_busy(msg);
|
|
|
|
if (!calls->ops->send_tones)
|
|
return dbus_gsm_not_implemented(msg);
|
|
|
|
/* Send DTMFs only if we have at least one connected call */
|
|
if (!voicecalls_have_connected(calls))
|
|
return dbus_gsm_failed(msg);
|
|
|
|
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &in_tones,
|
|
DBUS_TYPE_INVALID) == FALSE)
|
|
return dbus_gsm_invalid_args(msg);
|
|
|
|
len = strlen(in_tones);
|
|
|
|
if (len == 0)
|
|
return dbus_gsm_invalid_format(msg);
|
|
|
|
tones = g_ascii_strup(in_tones, len);
|
|
|
|
/* Tones can be 0-9, *, #, A-D according to 27.007 C.2.11 */
|
|
for (i = 0; i < len; i++) {
|
|
if (g_ascii_isdigit(tones[i]) ||
|
|
tones[i] == '*' || tones[i] == '#' ||
|
|
(tones[i] >= 'A' && tones[i] <= 'D'))
|
|
continue;
|
|
|
|
g_free(tones);
|
|
return dbus_gsm_invalid_format(msg);
|
|
}
|
|
|
|
calls->flags |= VOICECALLS_FLAG_PENDING;
|
|
calls->pending = dbus_message_ref(msg);
|
|
|
|
calls->ops->send_tones(modem, tones, generic_callback, calls);
|
|
|
|
g_free(tones);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static GDBusMethodTable manager_methods[] = {
|
|
{ "GetProperties", "", "a{sv}", manager_get_properties },
|
|
{ "Dial", "ss", "o", manager_dial,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "Transfer", "", "", manager_transfer,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "SwapCalls", "", "", manager_swap_calls,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "ReleaseAndAnswer", "", "", manager_release_and_answer,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "HoldAndAnswer", "", "", manager_hold_and_answer,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "HangupAll", "", "", manager_hangup_all,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "PrivateChat", "o", "ao", multiparty_private_chat,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "CreateMultiparty", "", "ao", multiparty_create,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "HangupMultiparty", "", "", multiparty_hangup,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ "SendTones", "s", "", manager_tone,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ }
|
|
};
|
|
|
|
static GDBusSignalTable manager_signals[] = {
|
|
{ "PropertyChanged", "sv" },
|
|
{ }
|
|
};
|
|
|
|
static gboolean real_emit_call_list_changed(void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *voicecalls = modem->voicecalls;
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
char **objpath_list;
|
|
|
|
voicecalls_path_list(modem, voicecalls->call_list, &objpath_list);
|
|
|
|
dbus_gsm_signal_array_property_changed(conn, modem->path,
|
|
VOICECALL_MANAGER_INTERFACE,
|
|
"Calls",
|
|
DBUS_TYPE_OBJECT_PATH,
|
|
&objpath_list);
|
|
|
|
dbus_gsm_free_string_array(objpath_list);
|
|
|
|
ofono_debug("Resetting updating flag");
|
|
voicecalls->flags &= ~VOICECALLS_FLAG_UPDATING_CALL_LIST;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void emit_call_list_changed(struct ofono_modem *modem)
|
|
{
|
|
#ifdef DELAY_EMIT
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
|
|
if (!(calls->flags & VOICECALLS_FLAG_UPDATING_CALL_LIST)) {
|
|
calls->flags |= VOICECALLS_FLAG_UPDATING_CALL_LIST;
|
|
g_timeout_add(0, real_emit_call_list_changed, modem);
|
|
}
|
|
#else
|
|
real_emit_call_list_changed(modem);
|
|
#endif
|
|
}
|
|
|
|
static gboolean real_emit_multiparty_call_list_changed(void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *voicecalls = modem->voicecalls;
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
char **objpath_list;
|
|
|
|
voicecalls_path_list(modem, voicecalls->multiparty_list, &objpath_list);
|
|
|
|
dbus_gsm_signal_array_property_changed(conn, modem->path,
|
|
VOICECALL_MANAGER_INTERFACE, "MultipartyCalls",
|
|
DBUS_TYPE_OBJECT_PATH,
|
|
&objpath_list);
|
|
|
|
dbus_gsm_free_string_array(objpath_list);
|
|
|
|
voicecalls->flags &= ~VOICECALLS_FLAG_UPDATING_MPTY_CALL_LIST;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void emit_multiparty_call_list_changed(struct ofono_modem *modem)
|
|
{
|
|
#ifdef DELAY_EMIT
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
|
|
if (!(calls->flags & VOICECALLS_FLAG_UPDATING_MPTY_CALL_LIST)) {
|
|
calls->flags |= VOICECALLS_FLAG_UPDATING_MPTY_CALL_LIST;
|
|
g_timeout_add(0, real_emit_multiparty_call_list_changed, modem);
|
|
}
|
|
#else
|
|
real_emit_multiparty_call_list_changed(modem);
|
|
#endif
|
|
}
|
|
|
|
void ofono_voicecall_disconnected(struct ofono_modem *modem, int id,
|
|
enum ofono_disconnect_reason reason,
|
|
const struct ofono_error *error)
|
|
{
|
|
GSList *l;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
struct voicecall *call;
|
|
|
|
ofono_debug("Got disconnection event for id: %d, reason: %d", id, reason);
|
|
|
|
l = g_slist_find_custom(calls->call_list, GINT_TO_POINTER(id),
|
|
call_compare_by_id);
|
|
|
|
if (!l) {
|
|
ofono_error("Plugin notified us of call disconnect for"
|
|
" unknown call");
|
|
return;
|
|
}
|
|
|
|
call = l->data;
|
|
|
|
l = g_slist_find_custom(calls->multiparty_list, GINT_TO_POINTER(id),
|
|
call_compare_by_id);
|
|
|
|
if (l) {
|
|
calls->multiparty_list =
|
|
g_slist_remove(calls->multiparty_list, call);
|
|
|
|
if (calls->multiparty_list->next == NULL) { /* Size == 1 */
|
|
g_slist_free(calls->multiparty_list);
|
|
calls->multiparty_list = 0;
|
|
}
|
|
|
|
emit_multiparty_call_list_changed(modem);
|
|
}
|
|
|
|
calls->release_list = g_slist_remove(calls->release_list, call);
|
|
|
|
modem_release_callid(modem, id);
|
|
|
|
/* TODO: Emit disconnect reason */
|
|
voicecall_set_call_status(modem, call, CALL_STATUS_DISCONNECTED);
|
|
|
|
voicecall_dbus_unregister(modem, call);
|
|
|
|
calls->call_list = g_slist_remove(calls->call_list, call);
|
|
|
|
emit_call_list_changed(modem);
|
|
}
|
|
|
|
void ofono_voicecall_notify(struct ofono_modem *modem, const struct ofono_call *call)
|
|
{
|
|
GSList *l;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
struct voicecall *v;
|
|
struct ofono_call *newcall = NULL;
|
|
|
|
ofono_debug("Got a voicecall event, status: %d, id: %u, number: %s",
|
|
call->status, call->id, call->phone_number);
|
|
|
|
l = g_slist_find_custom(calls->call_list, GINT_TO_POINTER(call->id),
|
|
call_compare_by_id);
|
|
|
|
if (l) {
|
|
ofono_debug("Found call with id: %d\n", call->id);
|
|
voicecall_set_call_status(modem, l->data, call->status);
|
|
voicecall_set_call_lineid(modem, l->data, call->phone_number,
|
|
call->number_type, call->clip_validity);
|
|
|
|
return;
|
|
}
|
|
|
|
ofono_debug("Did not find a call with id: %d\n", call->id);
|
|
|
|
newcall = g_try_new0(struct ofono_call, 1);
|
|
|
|
if (!call) {
|
|
ofono_error("Unable to allocate call");
|
|
goto err;
|
|
}
|
|
|
|
memcpy(newcall, call, sizeof(struct ofono_call));
|
|
|
|
if (modem_alloc_callid(modem) != call->id) {
|
|
ofono_error("Warning: Call id and internally tracked id"
|
|
" do not correspond");
|
|
goto err;
|
|
}
|
|
|
|
v = voicecall_create(modem, newcall);
|
|
|
|
if (!v) {
|
|
ofono_error("Unable to allocate voicecall_data");
|
|
goto err;
|
|
}
|
|
|
|
if (!voicecall_dbus_register(v)) {
|
|
ofono_error("Unable to register voice call");
|
|
goto err;
|
|
}
|
|
|
|
calls->call_list = g_slist_insert_sorted(calls->call_list, v,
|
|
call_compare);
|
|
|
|
emit_call_list_changed(modem);
|
|
|
|
return;
|
|
|
|
err:
|
|
if (newcall)
|
|
g_free(newcall);
|
|
|
|
if (v)
|
|
g_free(v);
|
|
}
|
|
|
|
static void generic_callback(const struct ofono_error *error, void *data)
|
|
{
|
|
struct voicecalls_data *calls = data;
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
DBusMessage *reply;
|
|
|
|
if (error->type != OFONO_ERROR_TYPE_NO_ERROR)
|
|
ofono_debug("command failed with error: %s",
|
|
telephony_error_to_str(error));
|
|
|
|
calls->flags &= ~VOICECALLS_FLAG_PENDING;
|
|
|
|
if (!calls->pending)
|
|
return;
|
|
|
|
if (error->type == OFONO_ERROR_TYPE_NO_ERROR)
|
|
reply = dbus_message_new_method_return(calls->pending);
|
|
else
|
|
reply = dbus_gsm_failed(calls->pending);
|
|
|
|
g_dbus_send_message(conn, reply);
|
|
|
|
dbus_message_unref(calls->pending);
|
|
calls->pending = NULL;
|
|
}
|
|
|
|
static void multirelease_callback(const struct ofono_error *error, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
DBusMessage *reply;
|
|
|
|
if (g_slist_length(calls->release_list)) {
|
|
voicecalls_release_next(modem);
|
|
return;
|
|
}
|
|
|
|
calls->flags &= ~VOICECALLS_FLAG_MULTI_RELEASE;
|
|
calls->flags &= ~VOICECALLS_FLAG_PENDING;
|
|
|
|
if (!calls->pending)
|
|
return;
|
|
|
|
reply = dbus_message_new_method_return(calls->pending);
|
|
|
|
g_dbus_send_message(conn, reply);
|
|
|
|
dbus_message_unref(calls->pending);
|
|
calls->pending = NULL;
|
|
}
|
|
|
|
static struct ofono_call *synthesize_outgoing_call(struct ofono_modem *modem,
|
|
DBusMessage *msg)
|
|
{
|
|
const char *number;
|
|
int number_type;
|
|
struct ofono_call *call;
|
|
|
|
call = g_try_new0(struct ofono_call, 1);
|
|
|
|
if (!call)
|
|
return call;
|
|
|
|
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
|
|
DBUS_TYPE_INVALID) == FALSE)
|
|
number = "";
|
|
else
|
|
string_to_phone_number(number, &number_type, &number);
|
|
|
|
call->id = modem_alloc_callid(modem);
|
|
|
|
if (call->id == 0) {
|
|
ofono_error("Failed to alloc callid, too many calls");
|
|
g_free(call);
|
|
return NULL;
|
|
}
|
|
|
|
call->direction = CALL_DIRECTION_MOBILE_ORIGINATED;
|
|
call->status = CALL_STATUS_DIALING;
|
|
strcpy(call->phone_number, number);
|
|
call->number_type = number_type;
|
|
call->clip_validity = CLIP_VALIDITY_VALID;
|
|
|
|
return call;
|
|
}
|
|
|
|
static void dial_callback(const struct ofono_error *error, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
DBusMessage *reply;
|
|
GSList *l;
|
|
struct ofono_call *call;
|
|
const char *path;
|
|
gboolean need_to_emit = FALSE;
|
|
|
|
if (error->type != OFONO_ERROR_TYPE_NO_ERROR)
|
|
ofono_debug("Dial callback returned error: %s",
|
|
telephony_error_to_str(error));
|
|
|
|
calls->flags &= ~VOICECALLS_FLAG_PENDING;
|
|
|
|
if (!calls->pending)
|
|
return;
|
|
|
|
if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
|
|
reply = dbus_gsm_failed(calls->pending);
|
|
g_dbus_send_message(conn, reply);
|
|
|
|
goto out;
|
|
}
|
|
|
|
reply = dbus_message_new_method_return(calls->pending);
|
|
if (!reply)
|
|
goto out;
|
|
|
|
/* Two things can happen, the call notification arrived before dial
|
|
* callback or dial callback was first. Handle here */
|
|
for (l = calls->call_list; l; l = l->next) {
|
|
struct voicecall *v = l->data;
|
|
|
|
if (v->call->status == CALL_STATUS_DIALING ||
|
|
v->call->status == CALL_STATUS_ALERTING)
|
|
break;
|
|
}
|
|
|
|
if (!l) {
|
|
struct voicecall *v;
|
|
call = synthesize_outgoing_call(modem, calls->pending);
|
|
|
|
if (!call) {
|
|
reply = dbus_gsm_failed(calls->pending);
|
|
g_dbus_send_message(conn, reply);
|
|
|
|
goto out;
|
|
}
|
|
|
|
v = voicecall_create(modem, call);
|
|
|
|
if (!v) {
|
|
reply = dbus_gsm_failed(calls->pending);
|
|
g_dbus_send_message(conn, reply);
|
|
|
|
goto out;
|
|
}
|
|
|
|
ofono_debug("Registering new call: %d", call->id);
|
|
voicecall_dbus_register(voicecall_create(modem, call));
|
|
|
|
calls->call_list = g_slist_insert_sorted(calls->call_list, v,
|
|
call_compare);
|
|
|
|
need_to_emit = TRUE;
|
|
} else {
|
|
struct voicecall *v = l->data;
|
|
|
|
call = v->call;
|
|
}
|
|
|
|
path = voicecall_build_path(modem, call);
|
|
|
|
dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &path,
|
|
DBUS_TYPE_INVALID);
|
|
|
|
g_dbus_send_message(conn, reply);
|
|
|
|
if (need_to_emit)
|
|
emit_call_list_changed(modem);
|
|
|
|
out:
|
|
dbus_message_unref(calls->pending);
|
|
calls->pending = NULL;
|
|
}
|
|
|
|
|
|
static void multiparty_callback_common(struct ofono_modem *modem,
|
|
DBusMessage *reply)
|
|
{
|
|
struct voicecalls_data *voicecalls = modem->voicecalls;
|
|
DBusMessageIter iter;
|
|
DBusMessageIter array_iter;
|
|
char **objpath_list;
|
|
int i;
|
|
|
|
voicecalls_path_list(modem, voicecalls->multiparty_list, &objpath_list);
|
|
|
|
dbus_message_iter_init_append(reply, &iter);
|
|
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
|
|
DBUS_TYPE_OBJECT_PATH_AS_STRING, &array_iter);
|
|
|
|
for (i = 0; objpath_list[i]; i++)
|
|
dbus_message_iter_append_basic(&array_iter,
|
|
DBUS_TYPE_OBJECT_PATH, &objpath_list[i]);
|
|
|
|
dbus_message_iter_close_container(&iter, &array_iter);
|
|
}
|
|
|
|
static void multiparty_create_callback(const struct ofono_error *error, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
DBusMessage *reply;
|
|
gboolean need_to_emit = FALSE;
|
|
|
|
if (error->type != OFONO_ERROR_TYPE_NO_ERROR)
|
|
ofono_debug("command failed with error: %s",
|
|
telephony_error_to_str(error));
|
|
|
|
calls->flags &= ~VOICECALLS_FLAG_PENDING;
|
|
|
|
if (!calls->pending)
|
|
return;
|
|
|
|
if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
|
|
reply = dbus_gsm_failed(calls->pending);
|
|
goto out;
|
|
}
|
|
|
|
/* We just created a multiparty call, gather all held
|
|
* active calls and add them to the multiparty list
|
|
*/
|
|
if (calls->multiparty_list) {
|
|
g_slist_free(calls->multiparty_list);
|
|
calls->multiparty_list = 0;
|
|
}
|
|
|
|
calls->multiparty_list = g_slist_concat(calls->multiparty_list,
|
|
voicecalls_held_list(calls));
|
|
|
|
calls->multiparty_list = g_slist_concat(calls->multiparty_list,
|
|
voicecalls_active_list(calls));
|
|
|
|
calls->multiparty_list = g_slist_sort(calls->multiparty_list,
|
|
call_compare);
|
|
|
|
if (g_slist_length(calls->multiparty_list) < 2) {
|
|
ofono_error("Created multiparty call, but size is less than 2"
|
|
" panic!");
|
|
|
|
reply = dbus_gsm_failed(calls->pending);
|
|
} else {
|
|
reply = dbus_message_new_method_return(calls->pending);
|
|
|
|
multiparty_callback_common(modem, reply);
|
|
need_to_emit = TRUE;
|
|
}
|
|
|
|
out:
|
|
g_dbus_send_message(conn, reply);
|
|
|
|
if (need_to_emit)
|
|
emit_multiparty_call_list_changed(modem);
|
|
|
|
dbus_message_unref(calls->pending);
|
|
calls->pending = NULL;
|
|
}
|
|
|
|
static void private_chat_callback(const struct ofono_error *error, void *data)
|
|
{
|
|
struct ofono_modem *modem = data;
|
|
struct voicecalls_data *calls = modem->voicecalls;
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
DBusMessage *reply;
|
|
gboolean need_to_emit = FALSE;
|
|
const char *callpath;
|
|
const char *c;
|
|
int id;
|
|
GSList *l;
|
|
|
|
if (error->type != OFONO_ERROR_TYPE_NO_ERROR)
|
|
ofono_debug("command failed with error: %s",
|
|
telephony_error_to_str(error));
|
|
|
|
calls->flags &= ~VOICECALLS_FLAG_PENDING;
|
|
|
|
if (!calls->pending)
|
|
return;
|
|
|
|
if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {
|
|
reply = dbus_gsm_failed(calls->pending);
|
|
goto out;
|
|
}
|
|
|
|
dbus_message_get_args(calls->pending, NULL,
|
|
DBUS_TYPE_OBJECT_PATH, &callpath,
|
|
DBUS_TYPE_INVALID);
|
|
|
|
c = strrchr(callpath, '/');
|
|
sscanf(c, "/voicecall%2u", &id);
|
|
|
|
l = g_slist_find_custom(calls->multiparty_list, GINT_TO_POINTER(id),
|
|
call_compare_by_id);
|
|
|
|
if (l) {
|
|
calls->multiparty_list =
|
|
g_slist_remove(calls->multiparty_list, l->data);
|
|
|
|
if (g_slist_length(calls->multiparty_list) < 2) {
|
|
g_slist_free(calls->multiparty_list);
|
|
calls->multiparty_list = 0;
|
|
}
|
|
}
|
|
|
|
reply = dbus_message_new_method_return(calls->pending);
|
|
|
|
multiparty_callback_common(modem, reply);
|
|
need_to_emit = TRUE;
|
|
|
|
out:
|
|
g_dbus_send_message(conn, reply);
|
|
|
|
if (need_to_emit)
|
|
emit_multiparty_call_list_changed(modem);
|
|
|
|
dbus_message_unref(calls->pending);
|
|
calls->pending = NULL;
|
|
}
|
|
|
|
int ofono_voicecall_register(struct ofono_modem *modem, struct ofono_voicecall_ops *ops)
|
|
{
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
|
|
if (modem == NULL)
|
|
return -1;
|
|
|
|
if (ops == NULL)
|
|
return -1;
|
|
|
|
modem->voicecalls = voicecalls_create();
|
|
|
|
if (modem->voicecalls == NULL)
|
|
return -1;
|
|
|
|
modem->voicecalls->ops = ops;
|
|
|
|
if (!g_dbus_register_interface(conn, modem->path,
|
|
VOICECALL_MANAGER_INTERFACE,
|
|
manager_methods, manager_signals, NULL,
|
|
modem, voicecalls_destroy)) {
|
|
ofono_error("Could not create %s interface",
|
|
VOICECALL_MANAGER_INTERFACE);
|
|
|
|
voicecalls_destroy(modem->voicecalls);
|
|
|
|
return -1;
|
|
}
|
|
|
|
modem_add_interface(modem, VOICECALL_MANAGER_INTERFACE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ofono_voicecall_unregister(struct ofono_modem *modem)
|
|
{
|
|
DBusConnection *conn = dbus_gsm_connection();
|
|
|
|
if (!modem->voicecalls)
|
|
return;
|
|
|
|
modem_remove_interface(modem, VOICECALL_MANAGER_INTERFACE);
|
|
g_dbus_unregister_interface(conn, modem->path,
|
|
VOICECALL_MANAGER_INTERFACE);
|
|
|
|
modem->voicecalls = NULL;
|
|
}
|