From df3b0c1cd8be029ff2c98c2ba7ba1110385a17f6 Mon Sep 17 00:00:00 2001 From: Alexander Couzens Date: Tue, 25 Jul 2017 15:31:48 +0200 Subject: [PATCH] [RFC] qmimodem: implement voice calls The voice_generated.* files is an RFC how files should look like. They aren't yet generated. --- Makefile.am | 5 +- drivers/qmimodem/qmi.h | 13 ++ drivers/qmimodem/voice.c | 86 ++++++++ drivers/qmimodem/voice.h | 84 ++++++++ drivers/qmimodem/voice_generated.c | 210 ++++++++++++++++++ drivers/qmimodem/voice_generated.h | 113 ++++++++++ drivers/qmimodem/voicecall.c | 332 ++++++++++++++++++++++++++++- 7 files changed, 840 insertions(+), 3 deletions(-) create mode 100644 drivers/qmimodem/voice.c create mode 100644 drivers/qmimodem/voice.h create mode 100644 drivers/qmimodem/voice_generated.c create mode 100644 drivers/qmimodem/voice_generated.h diff --git a/Makefile.am b/Makefile.am index 2a7e446e..614bdc2d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -216,7 +216,9 @@ qmi_sources = drivers/qmimodem/qmi.h drivers/qmimodem/qmi.c \ drivers/qmimodem/wds.h \ drivers/qmimodem/pds.h \ drivers/qmimodem/common.h \ - drivers/qmimodem/wda.h + drivers/qmimodem/wda.h \ + drivers/qmimodem/voice.h \ + drivers/qmimodem/voice.c builtin_modules += qmimodem builtin_sources += $(qmi_sources) \ @@ -225,6 +227,7 @@ builtin_sources += $(qmi_sources) \ drivers/qmimodem/qmimodem.c \ drivers/qmimodem/devinfo.c \ drivers/qmimodem/voicecall.c \ + drivers/qmimodem/voice_generated.c \ drivers/qmimodem/network-registration.c \ drivers/qmimodem/sim-legacy.c \ drivers/qmimodem/sim.c \ diff --git a/drivers/qmimodem/qmi.h b/drivers/qmimodem/qmi.h index d244cd89..f0453cc6 100644 --- a/drivers/qmimodem/qmi.h +++ b/drivers/qmimodem/qmi.h @@ -19,6 +19,9 @@ * */ +#ifndef __OFONO_QMI_QMI_H +#define __OFONO_QMI_QMI_H + #include #include @@ -173,3 +176,13 @@ uint16_t qmi_service_register(struct qmi_service *service, void *user_data, qmi_destroy_func_t destroy); bool qmi_service_unregister(struct qmi_service *service, uint16_t id); bool qmi_service_unregister_all(struct qmi_service *service); + + +/* FIXME: find a place for parse_error */ +enum parse_error { + NONE = 0, + MISSING_MANDATORY = 1, + INVALID_LENGTH = 2, +}; + +#endif /* __OFONO_QMI_QMI_H */ diff --git a/drivers/qmimodem/voice.c b/drivers/qmimodem/voice.c new file mode 100644 index 00000000..c0856176 --- /dev/null +++ b/drivers/qmimodem/voice.c @@ -0,0 +1,86 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2017 Alexander Couzens + * + * 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. + * + */ + +#include + +#include "voice.h" +#include "../../src/common.h" + +#define _(X) case X: return #X + +const char *qmi_voice_call_state_name(enum qmi_voice_call_state value) +{ + switch (value) { + _(QMI_CALL_STATE_IDLE); + _(QMI_CALL_STATE_ORIG); + _(QMI_CALL_STATE_INCOMING); + _(QMI_CALL_STATE_CONV); + _(QMI_CALL_STATE_CC_IN_PROG); + _(QMI_CALL_STATE_ALERTING); + _(QMI_CALL_STATE_HOLD); + _(QMI_CALL_STATE_WAITING); + _(QMI_CALL_STATE_DISCONNECTING); + _(QMI_CALL_STATE_END); + _(QMI_CALL_STATE_SETUP); + } + return "QMI_CALL_STATE_"; +} + +int qmi_to_ofono_status(uint8_t status, int *ret) { + int err = 0; + switch (status) { + case QMI_CALL_STATE_IDLE: + case QMI_CALL_STATE_END: + case QMI_CALL_STATE_DISCONNECTING: + *ret = CALL_STATUS_DISCONNECTED; + break; + case QMI_CALL_STATE_HOLD: + *ret = CALL_STATUS_HELD; + break; + case QMI_CALL_STATE_WAITING: + *ret = CALL_STATUS_WAITING; + break; + case QMI_CALL_STATE_ORIG: + *ret = CALL_STATUS_DIALING; + break; + case QMI_CALL_STATE_INCOMING: + *ret = CALL_STATUS_INCOMING; + break; + case QMI_CALL_STATE_CONV: + *ret = CALL_STATUS_ACTIVE; + break; + case QMI_CALL_STATE_CC_IN_PROG: + case QMI_CALL_STATE_SETUP: + /* FIXME: unsure if _SETUP is dialing or not */ + *ret = CALL_STATUS_DIALING; + break; + case QMI_CALL_STATE_ALERTING: + *ret = CALL_STATUS_ALERTING; + break; + default: + err = 1; + } + return err; +} + +uint8_t ofono_to_qmi_direction(enum call_direction ofono_direction) { + return ofono_direction + 1; +} +enum call_direction qmi_to_ofono_direction(uint8_t qmi_direction) { + return qmi_direction - 1; +} + diff --git a/drivers/qmimodem/voice.h b/drivers/qmimodem/voice.h new file mode 100644 index 00000000..f25648f7 --- /dev/null +++ b/drivers/qmimodem/voice.h @@ -0,0 +1,84 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2017 Alexander Couzens + * + * 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. + * + */ + +enum call_direction; + +enum ussd_dcs { + USS_DCS_ASCII = 0x1, + USS_DCS_8BIT, + USS_DCS_UCS2, +}; + +enum ussd_user_required { + NO_USER_ACTION_REQUIRED = 0x1, + USER_ACTION_REQUIRED = 0x2, +}; + +struct qmi_ussd_data { + uint8_t dcs; + uint8_t length; + uint8_t data[0]; +} __attribute__((__packed__)); + +enum voice_commands { + QMI_VOICE_CANCEL_USSD = 0x3c, + QMI_VOICE_USSD_RELEASE_IND = 0x3d, + QMI_VOICE_USSD_IND = 0x3e, + QMI_VOICE_SUPS_IND = 0x42, + QMI_VOICE_ASYNC_ORIG_USSD = 0x43, +}; + +enum qmi_voice_call_state { + QMI_CALL_STATE_IDLE = 0x0, + QMI_CALL_STATE_ORIG, + QMI_CALL_STATE_INCOMING, + QMI_CALL_STATE_CONV, + QMI_CALL_STATE_CC_IN_PROG, + QMI_CALL_STATE_ALERTING, + QMI_CALL_STATE_HOLD, + QMI_CALL_STATE_WAITING, + QMI_CALL_STATE_DISCONNECTING, + QMI_CALL_STATE_END, + QMI_CALL_STATE_SETUP +}; + +enum qmi_voice_call_type { + QMI_CALL_TYPE_VOICE = 0x0, + QMI_CALL_TYPE_VOICE_FORCE, +}; + +const char *qmi_voice_call_state_name(enum qmi_voice_call_state value); +uint8_t ofono_to_qmi_direction(enum call_direction ofono_direction); +enum call_direction qmi_to_ofono_direction(uint8_t qmi_direction); +int qmi_to_ofono_status(uint8_t status, int *ret); + +#define QMI_VOICE_IND_ALL_STATUS 0x2e + +#define QMI_VOICE_PARAM_USS_DATA 0x01 + +#define QMI_VOICE_PARAM_ASYNC_USSD_ERROR 0x10 +#define QMI_VOICE_PARAM_ASYNC_USSD_FAILURE_CASE 0x11 +#define QMI_VOICE_PARAM_ASYNC_USSD_DATA 0x12 + +#define QMI_VOICE_PARAM_USSD_IND_USER_ACTION 0x01 +#define QMI_VOICE_PARAM_USSD_IND_DATA 0x10 +#define QMI_VOICE_PARAM_USSD_IND_UCS2 0x11 + +/* according to GSM TS 23.038 */ +#define USSD_DCS_8BIT 0xf4 +#define USSD_DCS_UCS2 0x48 +#define USSD_DCS_UNSPECIFIC 0x0f diff --git a/drivers/qmimodem/voice_generated.c b/drivers/qmimodem/voice_generated.c new file mode 100644 index 00000000..3440be43 --- /dev/null +++ b/drivers/qmimodem/voice_generated.c @@ -0,0 +1,210 @@ + +#include +#include +#include + +#include "voice_generated.h" + +int qmi_voice_dial_call( + struct qmi_voice_dial_call_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy) +{ + struct qmi_param *param = NULL; + + param = qmi_param_new(); + if (!param) + goto error; + + if (arg->calling_number_set) { + if (!qmi_param_append(param, + 0x1, + strlen(arg->calling_number), + arg->calling_number)) + goto error; + } + + if (arg->call_type_set) + qmi_param_append_uint8(param, 0x10, arg->call_type); + + if (qmi_service_send(service, + 0x20, + param, + func, + user_data, + destroy) > 0) + return 0; +error: + g_free(param); + return 1; +} + +enum parse_error qmi_voice_dial_call_parse( + struct qmi_result *qmi_result, + struct qmi_voice_dial_call_result *result) +{ + int err = NONE; + + /* mandatory */ + if (qmi_result_get_uint8(qmi_result, 0x10, &result->call_id)) + result->call_id_set = 1; + else + err = MISSING_MANDATORY; + + return err; +} + +int qmi_voice_end_call( + struct qmi_voice_end_call_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy) +{ + struct qmi_param *param = NULL; + + param = qmi_param_new(); + if (!param) + goto error; + + if (arg->call_id_set) { + if (!qmi_param_append_uint8( + param, + 0x1, + arg->call_id)) + goto error; + } + + if (qmi_service_send(service, + 0x21, + param, + func, + user_data, + destroy) > 0) + return 0; +error: + g_free(param); + return 1; +} + +enum parse_error qmi_voice_end_call_parse( + struct qmi_result *qmi_result, + struct qmi_voice_end_call_result *result) +{ + int err = NONE; + + /* optional */ + if (qmi_result_get_uint8(qmi_result, 0x10, &result->call_id)) + result->call_id_set = 1; + + return err; +} + + +int qmi_voice_answer_call( + struct qmi_voice_answer_call_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy) +{ + struct qmi_param *param = NULL; + + param = qmi_param_new(); + if (!param) + goto error; + + if (arg->call_id_set) { + if (!qmi_param_append_uint8( + param, + 0x1, + arg->call_id)) + goto error; + } + + if (qmi_service_send(service, + 0x22, + param, + func, + user_data, + destroy) > 0) + return 0; +error: + g_free(param); + return 1; +} + + +enum parse_error qmi_voice_answer_call_parse( + struct qmi_result *qmi_result, + struct qmi_voice_answer_call_result *result) +{ + int err = NONE; + + /* optional */ + if (qmi_result_get_uint8(qmi_result, 0x10, &result->call_id)) + result->call_id_set = 1; + + return err; +} + +enum parse_error qmi_voice_ind_call_status( + struct qmi_result *qmi_result, + struct qmi_voice_all_call_status_ind *result) +{ + int err = NONE; + int offset; + uint16_t len; + const struct qmi_voice_remote_party_number *remote_party_number; + const struct qmi_voice_call_information *call_information; + + /* mandatory */ + call_information = qmi_result_get(qmi_result, 0x01, &len); + if (call_information) + { + int instance_size = sizeof(struct qmi_voice_call_information_instance); + /* verify the length */ + if (len < sizeof(call_information->size)) + return INVALID_LENGTH; + + if (len != call_information->size * sizeof(struct qmi_voice_call_information_instance) + + sizeof(call_information->size)) + return INVALID_LENGTH; + result->call_information_set = 1; + result->call_information = call_information; + } else + return MISSING_MANDATORY; + + /* mandatory */ + remote_party_number = qmi_result_get(qmi_result, 0x10, &len); + if (remote_party_number) { + const struct qmi_voice_remote_party_number_instance *instance; + int instance_size = sizeof(struct qmi_voice_remote_party_number_instance); + int i; + + /* verify the length */ + if (len < sizeof(remote_party_number->size)) + return INVALID_LENGTH; + + for (i = 0, offset = sizeof(remote_party_number->size); + offset <= len && i < 16 && i < remote_party_number->size; i++) + { + if (offset == len) { + break; + } else if (offset + instance_size > len) { + return INVALID_LENGTH; + } + + instance = (void *)remote_party_number + offset; + result->remote_party_number[i] = instance; + offset += sizeof(struct qmi_voice_remote_party_number_instance) + instance->number_size; + } + result->remote_party_number_set = 1; + result->remote_party_number_size = remote_party_number->size; + } else + return MISSING_MANDATORY; + + return err; +} diff --git a/drivers/qmimodem/voice_generated.h b/drivers/qmimodem/voice_generated.h new file mode 100644 index 00000000..471b52ea --- /dev/null +++ b/drivers/qmimodem/voice_generated.h @@ -0,0 +1,113 @@ + +#ifndef __OFONO_QMI_VOICE_GENERATED_H +#define __OFONO_QMI_VOICE_GENERATED_H + +#include "qmi.h" + +struct qmi_voice_remote_party_number_instance { + uint8_t call_id; + uint8_t presentation_indicator; + uint8_t number_size; + char number[0]; +} __attribute__((__packed__)); + +struct qmi_voice_remote_party_number { + uint8_t size; + struct qmi_voice_remote_party_number_instance instance[0]; +} __attribute__((__packed__)); + +/* generator / parser */ + +struct qmi_voice_dial_call_arg { + bool calling_number_set; + const char *calling_number; + bool call_type_set; + uint8_t call_type; +}; + +int qmi_voice_dial_call( + struct qmi_voice_dial_call_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy); + +struct qmi_voice_dial_call_result { + bool call_id_set; + uint8_t call_id; +}; + +enum parse_error qmi_voice_dial_call_parse( + struct qmi_result *qmi_result, + struct qmi_voice_dial_call_result *result); + +struct qmi_voice_end_call_arg { + bool call_id_set; + uint8_t call_id; +}; + +int qmi_voice_end_call( + struct qmi_voice_end_call_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy); + +struct qmi_voice_end_call_result { + bool call_id_set; + uint8_t call_id; +}; + +enum parse_error qmi_voice_end_call_parse( + struct qmi_result *qmi_result, + struct qmi_voice_end_call_result *result); + +struct qmi_voice_answer_call_arg { + bool call_id_set; + uint8_t call_id; +}; + +int qmi_voice_answer_call( + struct qmi_voice_answer_call_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy); + +struct qmi_voice_answer_call_result { + bool call_id_set; + uint8_t call_id; +}; + +enum parse_error qmi_voice_answer_call_parse( + struct qmi_result *qmi_result, + struct qmi_voice_answer_call_result *result); + +struct qmi_voice_call_information_instance { + uint8_t id; + uint8_t state; + uint8_t type; + uint8_t direction; + uint8_t mode; + uint8_t multipart_indicator; + uint8_t als; +} __attribute__((__packed__)); + +struct qmi_voice_call_information { + uint8_t size; + struct qmi_voice_call_information_instance instance[0]; +} __attribute__((__packed__)) ; + +struct qmi_voice_all_call_status_ind { + bool call_information_set; + const struct qmi_voice_call_information *call_information; + bool remote_party_number_set; + uint8_t remote_party_number_size; + const struct qmi_voice_remote_party_number_instance *remote_party_number[16]; +}; + +enum parse_error qmi_voice_ind_call_status( + struct qmi_result *qmi_result, + struct qmi_voice_all_call_status_ind *result); + +#endif /* __OFONO_QMI_VOICE_GENERATED_H */ diff --git a/drivers/qmimodem/voicecall.c b/drivers/qmimodem/voicecall.c index 29166b08..27aae40c 100644 --- a/drivers/qmimodem/voicecall.c +++ b/drivers/qmimodem/voicecall.c @@ -3,6 +3,7 @@ * oFono - Open Source Telephony * * Copyright (C) 2011-2012 Intel Corporation. All rights reserved. + * Copyright (C) 2017 Alexander Couzens * * 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 @@ -23,20 +24,113 @@ #include #endif +#include + #include #include #include +#include + +#include "../src/common.h" #include "qmi.h" - #include "qmimodem.h" +#include "voice.h" +#include "voice_generated.h" + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + + +/* qmi protocol */ + + +/* end of qmi */ struct voicecall_data { struct qmi_service *voice; uint16_t major; uint16_t minor; + GSList *call_list; + struct voicecall_static *vs; + struct ofono_phone_number dialed; }; +static void all_call_status_ind(struct qmi_result *result, void *user_data) +{ + struct ofono_voicecall *vc = user_data; + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + GSList *calls = NULL; + int i; + int size = 0; + struct qmi_voice_all_call_status_ind status_ind; + GSList *n, *o; + struct ofono_call *nc, *oc; + + + if (qmi_voice_ind_call_status(result, &status_ind) != NONE) { + DBG("Parsing of all call status indication failed"); + return; + } + + if (!status_ind.remote_party_number_set || !status_ind.call_information_set) { + DBG("Some required fields are not set"); + return; + } + + size = status_ind.call_information->size; + if (!size) { + DBG("No call informations received!"); + return; + } + + /* expect we have valid fields for every call */ + if (size != status_ind.remote_party_number_size) { + DBG("Not all fields have the same size"); + return; + } + + for (i = 0; i < size; i++) { + struct qmi_voice_call_information_instance call_info; + struct ofono_call *call; + const struct qmi_voice_remote_party_number_instance *remote_party = status_ind.remote_party_number[i]; + int number_size; + + call_info = status_ind.call_information->instance[i]; + call = g_new0(struct ofono_call, 1); + call->id = call_info.id; + call->direction = qmi_to_ofono_direction(call_info.direction); + + if (qmi_to_ofono_status(call_info.state, &call->status)) { + DBG("Ignore call id %d, because can not convert QMI state 0x%x to ofono.", + call_info.id, call_info.state); + continue; + } + DBG("Call %d in state %s(%d)", + call_info.id, + qmi_voice_call_state_name(call_info.state), + call_info.state); + + call->type = 0; /* always voice */ + number_size = remote_party->number_size; + if (number_size > OFONO_MAX_PHONE_NUMBER_LENGTH) + OFONO_MAX_PHONE_NUMBER_LENGTH; + strncpy(call->phone_number.number, remote_party->number, + number_size); + /* FIXME: set phone_number_type */ + + if (strlen(call->phone_number.number) > 0) + call->clip_validity = 0; + else + call->clip_validity = 2; + + calls = g_slist_insert_sorted(calls, call, ofono_call_compare); + } + + ofono_call_list_notify(vc, &vd->call_list, calls); +} + static void create_voice_cb(struct qmi_service *service, void *user_data) { struct ofono_voicecall *vc = user_data; @@ -58,6 +152,12 @@ static void create_voice_cb(struct qmi_service *service, void *user_data) data->voice = qmi_service_ref(service); + /* FIXME: we should call indication_register to ensure we get notified on call events. + * We rely at the moment on the default value of notifications + */ + qmi_service_register(data->voice, QMI_VOICE_IND_ALL_STATUS, + all_call_status_ind, vc, NULL); + ofono_voicecall_register(vc); } @@ -77,7 +177,6 @@ static int qmi_voicecall_probe(struct ofono_voicecall *vc, create_voice_cb, vc, NULL); return 0; - } static void qmi_voicecall_remove(struct ofono_voicecall *vc) @@ -92,13 +191,242 @@ static void qmi_voicecall_remove(struct ofono_voicecall *vc) qmi_service_unref(data->voice); + g_slist_free_full(data->call_list, g_free); + g_free(data); } + +static struct ofono_call *create_call(struct ofono_voicecall *vc, + enum call_direction direction, + enum call_status status, + const char *num, + int num_type, + int clip) +{ + return NULL; +} + +static void dial_cb(struct qmi_result *result, void *user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_voicecall *vc = cbd->user; + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + ofono_voicecall_cb_t cb = cbd->cb; + uint16_t error; + struct qmi_voice_dial_call_result dial_result; + struct ofono_call *call; + + if (qmi_result_set_error(result, &error)) { + DBG("QMI Error %d", error); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + if (NONE != qmi_voice_dial_call_parse(result, &dial_result)) { + DBG("Received invalid Result"); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + if (!dial_result.call_id_set) { + DBG("Didn't receive a call id"); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + DBG("New call QMI id %d", dial_result.call_id); + ofono_call_list_dial_callback(vc, + &vd->call_list, + &vd->dialed, + dial_result.call_id); + + + /* FIXME: create a timeout on this call_id */ + CALLBACK_WITH_SUCCESS(cb, cbd->data); +} + +static void dial(struct ofono_voicecall *vc, const struct ofono_phone_number *ph, + enum ofono_clir_option clir, ofono_voicecall_cb_t cb, + void *data) +{ + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + struct cb_data *cbd = cb_data_new(cb, data); + struct qmi_voice_dial_call_arg arg; + + cbd->user = vc; + arg.calling_number_set = true; + arg.calling_number = ph->number; + memcpy(&vd->dialed, ph, sizeof(*ph)); + + arg.call_type_set = true; + arg.call_type = QMI_CALL_TYPE_VOICE_FORCE; + + if (!qmi_voice_dial_call( + &arg, + vd->voice, + dial_cb, + cbd, + g_free)) + return; + + CALLBACK_WITH_FAILURE(cb, data); + g_free(cbd); +} + +static void answer_cb(struct qmi_result *result, void *user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_voicecall *vc = cbd->user; + ofono_voicecall_cb_t cb = cbd->cb; + uint16_t error; + struct qmi_voice_answer_call_result answer_result; + struct ofono_call *call; + + if (qmi_result_set_error(result, &error)) { + DBG("QMI Error %d", error); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + /* TODO: what happens when calling it with no active call or wrong caller id? */ + if (NONE != qmi_voice_answer_call_parse(result, &answer_result)) { + DBG("Received invalid Result"); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + CALLBACK_WITH_SUCCESS(cb, cbd->data); +} + +static void answer(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, void *data) +{ + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + struct cb_data *cbd = cb_data_new(cb, data); + struct qmi_voice_answer_call_arg arg; + struct ofono_call *call; + GSList *list; + + DBG(""); + cbd->user = vc; + + list = g_slist_find_custom(vd->call_list, + GINT_TO_POINTER(CALL_STATUS_INCOMING), + ofono_call_compare_by_status); + + if (list == NULL) { + DBG("Can not find a call to answer"); + goto err; + } + + call = list->data; + + arg.call_id_set = true; + arg.call_id = call->id; + + if (!qmi_voice_answer_call( + &arg, + vd->voice, + answer_cb, + cbd, + g_free)) + return; +err: + CALLBACK_WITH_FAILURE(cb, data); + g_free(cbd); +} + +static void end_cb(struct qmi_result *result, void *user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_voicecall *vc = cbd->user; + ofono_voicecall_cb_t cb = cbd->cb; + uint16_t error; + struct qmi_voice_end_call_result end_result; + struct ofono_call *call; + + if (qmi_result_set_error(result, &error)) { + DBG("QMI Error %d", error); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + if (NONE != qmi_voice_end_call_parse(result, &end_result)) { + DBG("Received invalid Result"); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + CALLBACK_WITH_SUCCESS(cb, cbd->data); +} + +static void release_specific(struct ofono_voicecall *vc, int id, + ofono_voicecall_cb_t cb, void *data) +{ + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + struct cb_data *cbd = cb_data_new(cb, data); + struct qmi_voice_end_call_arg arg; + int i; + + DBG(""); + cbd->user = vc; + + arg.call_id_set = true; + arg.call_id = id; + + if (!qmi_voice_end_call(&arg, + vd->voice, + end_cb, + cbd, + g_free)) + return; + + CALLBACK_WITH_FAILURE(cb, data); + g_free(cbd); +} + +static void hangup_active(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + struct qmi_voice_end_call_arg arg; + struct ofono_call *call; + GSList *list = NULL; + enum call_status active[] = { + CALL_STATUS_ACTIVE, + CALL_STATUS_DIALING, + CALL_STATUS_ALERTING + }; + int i; + + DBG(""); + for (i = 0; i < ARRAY_SIZE(active); i++) { + list = g_slist_find_custom(vd->call_list, + GINT_TO_POINTER(CALL_STATUS_ACTIVE), + ofono_call_compare_by_status); + + if (list) + break; + } + + if (list == NULL) { + DBG("Can not find a call to hang up"); + CALLBACK_WITH_FAILURE(cb, data); + return; + } + + call = list->data; + release_specific(vc, call->id, cb, data); +} + static struct ofono_voicecall_driver driver = { .name = "qmimodem", .probe = qmi_voicecall_probe, .remove = qmi_voicecall_remove, + .dial = dial, + .answer = answer, + .hangup_active = hangup_active, + .release_specific = release_specific, }; void qmi_voicecall_init(void)