From a09aa1c6eaef93b20dfe5c65db00b39dd15f8c45 Mon Sep 17 00:00:00 2001 From: Alexander Couzens Date: Tue, 25 Jul 2017 12:42:29 +0200 Subject: [PATCH] add call-list helper to manage voice call lists Many drivers asks the modem for a complete call list of current calls. These list of calls can be feeded into call-list which parse the list and notify ofono for new calls. --- .gitignore | 1 + Makefile.am | 13 ++- include/call-list.h | 38 +++++++ src/call-list.c | 110 ++++++++++++++++++ unit/test-call-list.c | 252 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 411 insertions(+), 3 deletions(-) create mode 100644 include/call-list.h create mode 100644 src/call-list.c create mode 100644 unit/test-call-list.c diff --git a/.gitignore b/.gitignore index 2912af19..7f3c56f6 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ unit/test-rilmodem-cs unit/test-rilmodem-gprs unit/test-rilmodem-sms unit/test-mbim +unit/test-call-list unit/test-*.log unit/test-*.trs test-driver diff --git a/Makefile.am b/Makefile.am index fbb0eff4..92db11fa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -79,7 +79,7 @@ pkginclude_HEADERS = include/log.h include/plugin.h include/history.h \ include/cdma-provision.h include/handsfree.h \ include/handsfree-audio.h include/siri.h \ include/netmon.h include/lte.h include/ims.h \ - include/storage.h + include/storage.h include/call-list.h nodist_pkginclude_HEADERS = include/version.h @@ -724,7 +724,7 @@ src_ofonod_SOURCES = $(builtin_sources) $(gatchat_sources) \ src/handsfree-audio.c src/bluetooth.h \ src/hfp.h src/siri.c \ src/netmon.c src/lte.c src/ims.c \ - src/netmonagent.c src/netmonagent.h + src/netmonagent.c src/netmonagent.h src/call-list.c src_ofonod_LDADD = gdbus/libgdbus-internal.la $(builtin_libadd) $(ell_ldadd) \ @GLIB_LIBS@ @DBUS_LIBS@ -ldl @@ -906,7 +906,8 @@ unit_tests = unit/test-common unit/test-util \ unit/test-rilmodem-cs \ unit/test-rilmodem-sms \ unit/test-rilmodem-cb \ - unit/test-rilmodem-gprs + unit/test-rilmodem-gprs \ + unit/test-call-list noinst_PROGRAMS = $(unit_tests) \ unit/test-sms-root unit/test-mux unit/test-caif @@ -944,6 +945,12 @@ unit_test_sms_root_SOURCES = unit/test-sms-root.c \ unit_test_sms_root_LDADD = @GLIB_LIBS@ $(ell_ldadd) unit_objects += $(unit_test_sms_root_OBJECTS) +unit_test_call_list_SOURCES = \ + src/common.c src/util.c \ + src/call-list.c unit/test-call-list.c +unit_test_call_list_LDADD = @GLIB_LIBS@ $(ell_ldadd) +unit_objects += $(unit_test_call_list_OBJECTS) + unit_test_mux_SOURCES = unit/test-mux.c $(gatchat_sources) unit_test_mux_LDADD = @GLIB_LIBS@ unit_objects += $(unit_test_mux_OBJECTS) diff --git a/include/call-list.h b/include/call-list.h new file mode 100644 index 00000000..dbd6ddd6 --- /dev/null +++ b/include/call-list.h @@ -0,0 +1,38 @@ +/* + * + * 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 + +struct ofono_voicecall; +struct ofono_phone_number; + +/* + * Can be called by the driver in the dialing callback, + * when the new call id already known + */ +void ofono_call_list_dial_callback(struct ofono_voicecall *vc, + GSList **call_list, + const struct ofono_phone_number *ph, + int call_id); + +/* + * Called with a list of known calls e.g. clcc. + * Call list will take ownership of all ofono call within the calls. + */ +void ofono_call_list_notify(struct ofono_voicecall *vc, + GSList **call_list, + GSList *calls); diff --git a/src/call-list.c b/src/call-list.c new file mode 100644 index 00000000..b7f1650a --- /dev/null +++ b/src/call-list.c @@ -0,0 +1,110 @@ +/* + * + * 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 "common.h" + +#include +#include +#include +#include + +#include + +void ofono_call_list_dial_callback(struct ofono_voicecall *vc, + GSList **call_list, + const struct ofono_phone_number *ph, + int call_id) +{ + struct ofono_call *call; + GSList *list; + + /* check if call_id already present */ + list = g_slist_find_custom(*call_list, + GINT_TO_POINTER(call_id), + ofono_call_compare_by_id); + + if (list) { + return; + } + + call = g_new0(struct ofono_call, 1); + call->id = call_id; + + memcpy(&call->called_number, ph, sizeof(*ph)); + call->direction = CALL_DIRECTION_MOBILE_ORIGINATED; + call->status = CALL_STATUS_DIALING; + call->type = 0; /* voice */ + + *call_list = g_slist_insert_sorted(*call_list, + call, + ofono_call_compare); + ofono_voicecall_notify(vc, call); +} + +void ofono_call_list_notify(struct ofono_voicecall *vc, + GSList **call_list, + GSList *calls) +{ + GSList *old_calls = *call_list; + GSList *new_calls = calls; + struct ofono_call *new_call, *old_call; + + while (old_calls || new_calls) { + old_call = old_calls ? old_calls->data : NULL; + new_call = new_calls ? new_calls->data : NULL; + + /* we drop disconnected calls and treat them as not existent */ + if (new_call && new_call->status == CALL_STATUS_DISCONNECTED) { + new_calls = new_calls->next; + calls = g_slist_remove(calls, new_call); + g_free(new_call); + continue; + } + + if (old_call && + (new_call == NULL || + (new_call->id > old_call->id))) { + ofono_voicecall_disconnected( + vc, + old_call->id, + OFONO_DISCONNECT_REASON_UNKNOWN, + NULL); + old_calls = old_calls->next; + } else if (new_call && + (old_call == NULL || + (new_call->id < old_call->id))) { + + /* new call, signal it */ + if (new_call->type == 0) + ofono_voicecall_notify(vc, new_call); + + new_calls = new_calls->next; + } else { + if (memcmp(new_call, old_call, sizeof(*new_call)) + && new_call->type == 0) + ofono_voicecall_notify(vc, new_call); + + new_calls = new_calls->next; + old_calls = old_calls->next; + } + } + + g_slist_free_full(*call_list, g_free); + *call_list = calls; +} diff --git a/unit/test-call-list.c b/unit/test-call-list.c new file mode 100644 index 00000000..6b0ec387 --- /dev/null +++ b/unit/test-call-list.c @@ -0,0 +1,252 @@ +/* + * + * 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 + + +#include "../src/common.h" +#include +#include + +struct voicecall { +}; + +struct notified { + unsigned int id; + enum call_status status; +}; + +static struct notified notified_list[32]; +static int notified_idx; +static int notified_check; + +void reset_notified(void) +{ + notified_idx = 0; + notified_check = 0; + memset(¬ified_list, 0, sizeof(notified_list)); +} + +void ofono_voicecall_notify(struct ofono_voicecall *vc, + struct ofono_call *call) +{ + notified_list[notified_idx].id = call->id; + notified_list[notified_idx].status = call->status; + notified_idx++; +} + +void ofono_voicecall_disconnected(struct ofono_voicecall *vc, int id, + enum ofono_disconnect_reason reason, + const struct ofono_error *error) +{ + notified_list[notified_idx].id = id; + notified_list[notified_idx].status = CALL_STATUS_DISCONNECTED; + notified_idx++; +} + +static GSList *create_call( + GSList *calls, + unsigned int id, + enum call_status status, + enum call_direction direction) +{ + struct ofono_call *call = g_new0(struct ofono_call, 1); + + call->id = id; + call->status = status; + call->direction = direction; + + calls = g_slist_insert_sorted(calls, call, ofono_call_compare); + + return calls; +} + +static void assert_notified(unsigned int call_id, int call_status) +{ + g_assert(notified_idx >= notified_check); + g_assert(notified_list[notified_check].id == call_id); + g_assert(notified_list[notified_check].status == call_status); + + notified_check++; +} + +static void test_notify_disconnected(void) +{ + struct ofono_voicecall *vc = NULL; + struct ofono_phone_number ph; + GSList *call_list; + GSList *calls; + + strcpy(ph.number, "004888123456"); + ph.type = 0; + + /* reset test */ + reset_notified(); + call_list = NULL; + + /* fill disconnected call*/ + calls = create_call(NULL, 1, CALL_STATUS_DISCONNECTED, + CALL_DIRECTION_MOBILE_TERMINATED); + ofono_call_list_notify(vc, &call_list, calls); + + /* incoming call */ + calls = create_call(NULL, 1, CALL_STATUS_DISCONNECTED, + CALL_DIRECTION_MOBILE_TERMINATED); + calls = create_call(calls, 1, CALL_STATUS_ALERTING, + CALL_DIRECTION_MOBILE_TERMINATED); + ofono_call_list_notify(vc, &call_list, calls); + + /* answer call */ + calls = create_call(NULL, 1, CALL_STATUS_ACTIVE, + CALL_DIRECTION_MOBILE_TERMINATED); + calls = create_call(calls, 1, CALL_STATUS_DISCONNECTED, + CALL_DIRECTION_MOBILE_TERMINATED); + ofono_call_list_notify(vc, &call_list, calls); + + /* another call waiting */ + calls = create_call(NULL, 1, CALL_STATUS_DISCONNECTED, + CALL_DIRECTION_MOBILE_TERMINATED); + calls = create_call(calls, 1, CALL_STATUS_ACTIVE, + CALL_DIRECTION_MOBILE_TERMINATED); + calls = create_call(calls, 2, CALL_STATUS_DISCONNECTED, + CALL_DIRECTION_MOBILE_TERMINATED); + calls = create_call(calls, 2, CALL_STATUS_WAITING, + CALL_DIRECTION_MOBILE_TERMINATED); + calls = create_call(calls, 2, CALL_STATUS_DISCONNECTED, + CALL_DIRECTION_MOBILE_TERMINATED); + ofono_call_list_notify(vc, &call_list, calls); + + /* end all calls */ + ofono_call_list_notify(vc, &call_list, NULL); + + /* verify call history */ + assert_notified(1, CALL_STATUS_ALERTING); + assert_notified(1, CALL_STATUS_ACTIVE); + assert_notified(2, CALL_STATUS_WAITING); + assert_notified(1, CALL_STATUS_DISCONNECTED); + assert_notified(2, CALL_STATUS_DISCONNECTED); + + g_assert(notified_check == notified_idx); + g_slist_free_full(call_list, g_free); +} + +static void test_notify(void) +{ + struct ofono_voicecall *vc = NULL; + struct ofono_phone_number ph; + GSList *call_list; + GSList *calls; + + strcpy(ph.number, "004888123456"); + ph.type = 0; + + /* reset test */ + reset_notified(); + call_list = NULL; + + /* incoming call */ + calls = create_call(NULL, 1, CALL_STATUS_ALERTING, + CALL_DIRECTION_MOBILE_TERMINATED); + ofono_call_list_notify(vc, &call_list, calls); + + /* answer call */ + calls = create_call(NULL, 1, CALL_STATUS_ACTIVE, + CALL_DIRECTION_MOBILE_TERMINATED); + ofono_call_list_notify(vc, &call_list, calls); + + /* another call waiting */ + calls = create_call(NULL, 1, CALL_STATUS_ACTIVE, + CALL_DIRECTION_MOBILE_TERMINATED); + calls = create_call(calls, 2, CALL_STATUS_WAITING, + CALL_DIRECTION_MOBILE_TERMINATED); + ofono_call_list_notify(vc, &call_list, calls); + + /* end all calls */ + ofono_call_list_notify(vc, &call_list, NULL); + + /* verify call history */ + assert_notified(1, CALL_STATUS_ALERTING); + assert_notified(1, CALL_STATUS_ACTIVE); + assert_notified(2, CALL_STATUS_WAITING); + assert_notified(1, CALL_STATUS_DISCONNECTED); + assert_notified(2, CALL_STATUS_DISCONNECTED); + + g_assert(notified_check == notified_idx); + g_slist_free_full(call_list, g_free); +} + +static void test_dial_callback(void) +{ + struct ofono_voicecall *vc = NULL; + struct ofono_phone_number ph; + struct ofono_call *call; + GSList *call_list; + + /* reset test */ + reset_notified(); + call_list = NULL; + + strcpy(ph.number, "0099301234567890"); + ph.type = 0; + + ofono_call_list_dial_callback(vc, &call_list, &ph, 33); + + call = call_list->data; + + g_assert(strcmp(call->called_number.number, ph.number) == 0); + g_slist_free_full(call_list, g_free); +} + +static void test_dial_callback_race(void) +{ + struct ofono_voicecall *vc = NULL; + struct ofono_phone_number ph; + struct ofono_call *call; + GSList *call_list, *calls; + + /* reset test */ + reset_notified(); + call_list = NULL; + + strcpy(ph.number, "0099301234567890"); + ph.type = 0; + + /* outgoing call */ + calls = create_call(NULL, 1, CALL_STATUS_DIALING, + CALL_DIRECTION_MOBILE_ORIGINATED); + ofono_call_list_notify(vc, &call_list, calls); + ofono_call_list_dial_callback(vc, &call_list, &ph, 1); + + g_assert(call_list->next == NULL); + + /* check how many items in the variable */ + g_slist_free_full(call_list, g_free); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/test-call-list/dial_callback", test_dial_callback); + g_test_add_func("/test-call-list/dial_callback_race", test_dial_callback_race); + g_test_add_func("/test-call-list/test_notify", test_notify); + g_test_add_func("/test-call-list/test_notify_disconnected", + test_notify_disconnected); + return g_test_run(); +}