From: "Felipe F. Tonello" <eu@xxxxxxxxxxxxxxxxx> This module is used to change a card profile when a voice call is received from oFono. Signed-off-by: Felipe F. Tonello <eu at felipetonello.com> --- src/Makefile.am | 12 +- src/modules/module-ofono-switch-on-voicecall.c | 480 +++++++++++++++++++++++++ 2 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 src/modules/module-ofono-switch-on-voicecall.c diff --git a/src/Makefile.am b/src/Makefile.am index 1ac8a16..add00fb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1326,7 +1326,8 @@ endif if HAVE_DBUS modlibexec_LTLIBRARIES += \ module-rygel-media-server.la \ - module-dbus-protocol.la + module-dbus-protocol.la \ + module-ofono-switch-on-voicecall.la endif if HAVE_BLUEZ @@ -1457,7 +1458,8 @@ SYMDEF_FILES = \ module-switch-on-connect-symdef.h \ module-switch-on-port-available-symdef.h \ module-filter-apply-symdef.h \ - module-filter-heuristics-symdef.h + module-filter-heuristics-symdef.h \ + module-ofono-switch-on-voicecall-symdef.h if HAVE_ESOUND SYMDEF_FILES += \ @@ -2103,6 +2105,12 @@ module_rygel_media_server_la_LDFLAGS = $(MODULE_LDFLAGS) module_rygel_media_server_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libprotocol-http.la module_rygel_media_server_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) +# oFono VoiceCall switcher +module_ofono_switch_on_voicecall_la_SOURCES = modules/module-ofono-switch-on-voicecall.c +module_ofono_switch_on_voicecall_la_LDFLAGS = $(MODULE_LDFLAGS) +module_ofono_switch_on_voicecall_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) +module_ofono_switch_on_voicecall_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) + ################################### # Some minor stuff # ################################### diff --git a/src/modules/module-ofono-switch-on-voicecall.c b/src/modules/module-ofono-switch-on-voicecall.c new file mode 100644 index 0000000..786aedb --- /dev/null +++ b/src/modules/module-ofono-switch-on-voicecall.c @@ -0,0 +1,480 @@ +/*** + This file is part of PulseAudio. + + Copyright 2013 Felipe F. Tonello <eu at felipetonello.com> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio 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 Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-shared.h> +#include <pulsecore/strlist.h> +#include <pulsecore/hashmap.h> + +#include "module-ofono-switch-on-voicecall-symdef.h" + +PA_MODULE_AUTHOR("Felipe F. Tonello"); +PA_MODULE_DESCRIPTION("Card profile switcher while a call detected by oFono"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(false); +PA_MODULE_USAGE("card=<card to switch profile> profile=<profile to set during a call>"); + +static const char* const valid_modargs[] = { + "card", + "profile", + NULL, +}; + +struct userdata { + pa_card *card; + pa_card_profile *profile; + pa_card_profile *old_profile; + pa_dbus_connection *bus; + bool filter_added; + pa_hashmap *modems_hash; + char *voicecall_path; +}; + +static void modems_hash_free_value(void *p) { + pa_strlist_free((pa_strlist *)p); +} + +static const char *check_variant_property(DBusMessageIter *i) { + const char *key; + + pa_assert(i); + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { + pa_log("Property name not a string."); + return NULL; + } + + dbus_message_iter_get_basic(i, &key); + + if (!dbus_message_iter_next(i)) { + pa_log("Property value missing"); + return NULL; + } + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) { + pa_log("Property value not a variant."); + return NULL; + } + + return key; +} + +static inline bool update_profile(const struct userdata *u, pa_card_profile *p) { + pa_assert(u); + pa_assert(p); + + /* only change profile if the new profile is different */ + if (u->card->active_profile == p) + return false; + + if (pa_card_set_profile(u->card, p, false) < 0) { + pa_log_error("Couldn't set profile '%s' for card '%s'", p->name, u->card->name); + return false; + } + + pa_log_info("Set profile '%s' for card '%s'", p->name, u->card->name); + + return true; +} + +static bool switch_profile(const struct userdata *u) { + return update_profile(u, u->profile); +} + +static bool switch_back_profile(const struct userdata *u) { + return update_profile(u, u->old_profile); +} + +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) { + struct userdata *u = userdata; + DBusError error; + + pa_assert(bus); + pa_assert(message); + pa_assert(u); + + dbus_error_init(&error); + + pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", + dbus_message_get_interface(message), + dbus_message_get_path(message), + dbus_message_get_member(message)); + + if (dbus_message_is_signal(message, "org.ofono.Manager", "ModemAdded")) { + /* create new listener for the modem */ + + const char *path; + pa_strlist *filter_list = NULL; + char *call_added, *call_removed; + + if (!dbus_message_get_args(message, &error, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + pa_log_error("Failed to parse ModemAdded message: %s: %s", error.name, error.message); + goto finish; + } + + pa_log_debug("ModemAdded(object path, dict properties): path=%s", path); + + if (pa_hashmap_get(u->modems_hash, path)) { + pa_log_info("Modem already loaded: %s", path); + goto finish; + } + + call_added = pa_sprintf_malloc("type='signal',sender='org.ofono'," + "interface='org.ofono.VoiceCallManager'," + "path='%s',member='CallAdded'", path); + call_removed = pa_sprintf_malloc("type='signal',sender='org.ofono'," + "interface='org.ofono.VoiceCallManager'," + "path='%s',member='CallRemoved'", path); + + if (pa_dbus_add_matches(pa_dbus_connection_get(u->bus), &error, + call_added, call_removed, NULL) < 0) { + pa_log_error("Unable to subscribe to oFono VoiceCallManager signals: %s: %s", + error.name, error.message); + pa_xfree(call_added); + pa_xfree(call_removed); + goto finish; + } + + filter_list = pa_strlist_prepend(filter_list, call_added); + filter_list = pa_strlist_prepend(filter_list, call_removed); + pa_hashmap_put(u->modems_hash, (char *)path, filter_list); + + } else if (dbus_message_is_signal(message, "org.ofono.Manager", "ModemRemoved")) { + /* remove that listener */ + const char *path; + pa_strlist *filter_list = NULL, *l; + char *s; + + if (!dbus_message_get_args(message, &error, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + pa_log_error("Failed to parse ModemAdded message: %s: %s", error.name, error.message); + goto finish; + } + + pa_log("ModemRemoved(object path): path=%s", path); + + if (!(filter_list = pa_hashmap_remove(u->modems_hash, path))) { + pa_log_info("Modem '%s' not loaded", path); + goto finish; + } + + l = filter_list; + while (l) { + l = pa_strlist_pop(l, &s); + pa_dbus_remove_matches(pa_dbus_connection_get(u->bus), s, NULL); + pa_xfree(s); + } + + } else if (dbus_message_is_signal(message, "org.ofono.VoiceCallManager", "CallAdded")) { + /* check call properties if "state" is "dialing" or "incoming" */ + DBusMessageIter arg_i; + + if (u->voicecall_path) { + pa_log_error("VoiceCall was already loaded: '%s'", u->voicecall_path); + goto finish; + } + + if (!dbus_message_iter_init(message, &arg_i)) { + pa_log_error("Failed to parse ModemAdded: %s: %s", error.name, error.message); + goto finish; + } + + while (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_INVALID) { + const char *path; + + switch (dbus_message_iter_get_arg_type(&arg_i)) { + case DBUS_TYPE_OBJECT_PATH: { + dbus_message_iter_get_basic(&arg_i, &path); + break; + } + + case DBUS_TYPE_ARRAY: { + DBusMessageIter array_i; + dbus_message_iter_recurse(&arg_i, &array_i); + while (dbus_message_iter_get_arg_type(&array_i) != DBUS_TYPE_INVALID) { + DBusMessageIter dict_i; + + dbus_message_iter_recurse(&array_i, &dict_i); + while (dbus_message_iter_get_arg_type(&dict_i) != DBUS_TYPE_INVALID) { + const char *key; + + key = check_variant_property(&dict_i); + if (key) { + DBusMessageIter variant_i; + + dbus_message_iter_recurse(&dict_i, &variant_i); + + if (dbus_message_iter_get_arg_type(&variant_i) == DBUS_TYPE_STRING && + pa_streq(key, "State")) { + const char *state; + dbus_message_iter_get_basic(&variant_i, &state); + + if (pa_streq(state, "dialing") || pa_streq(state, "incoming")) { + char *filter; + + pa_log_debug("CallAdded(object path, dict properties): path=%s " + "state=%s", path, state); + + u->voicecall_path = pa_xstrdup(path); + filter = pa_sprintf_malloc("type='signal',sender='org.ofono'," + "interface='org.ofono.VoiceCall'," + "path='%s',member='PropertyChanged'", + u->voicecall_path); + + if (pa_dbus_add_matches(pa_dbus_connection_get(u->bus), &error, + filter, NULL) < 0) { + pa_log_error("Unable to subscribe to oFono VoiceCallManager signals: %s: %s", + error.name, error.message); + pa_xfree(filter); + goto finish; + } + pa_xfree(filter); + } + } + } + dbus_message_iter_next(&dict_i); + } + + dbus_message_iter_next(&array_i); + } + break; + } + } + dbus_message_iter_next(&arg_i); + } + } else if (dbus_message_is_signal(message, "org.ofono.VoiceCallManager", "CallRemoved")) { + const char *path; + char *filter; + + if (!dbus_message_get_args(message, &error, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + pa_log_error("Failed to parse ModemAdded message: %s: %s", error.name, error.message); + goto finish; + } + + pa_log_debug("CallRemoved(object path, dict properties): path=%s", path); + + if (!(u->voicecall_path && pa_streq(path, u->voicecall_path))) { + pa_log_error("VoiceCall '%s' not loaded", path); + goto finish; + } + + switch_back_profile(u); + + filter = pa_sprintf_malloc("type='signal',sender='org.ofono'," + "interface='org.ofono.VoiceCall'," + "path='%s',member='PropertyChanged'", + u->voicecall_path); + + pa_dbus_remove_matches(pa_dbus_connection_get(u->bus), filter, NULL); + + pa_xfree(filter); + pa_xfree(u->voicecall_path); + u->voicecall_path = NULL; + + } else if (dbus_message_is_signal(message, "org.ofono.VoiceCall", "PropertyChanged")) { + const char *path, *key; + DBusMessageIter arg_i, variant_i; + + path = dbus_message_get_path(message); + + if (!(u->voicecall_path && pa_streq(path, u->voicecall_path))) { + pa_log_error("VoiceCall '%s' not loaded", path); + goto finish; + } + + if (!dbus_message_iter_init(message, &arg_i)) { + pa_log_error("Failed to parse PropertyChange: %s: %s", error.name, error.message); + goto finish; + } + + key = check_variant_property(&arg_i); + if (!key) + goto finish; + + dbus_message_iter_recurse(&arg_i, &variant_i); + + if (dbus_message_iter_get_arg_type(&variant_i) == DBUS_TYPE_STRING && + pa_streq(key, "State")) { + const char *state; + + dbus_message_iter_get_basic(&variant_i, &state); + if (pa_streq(state, "alerting") || pa_streq(state, "active")) { + + pa_log_debug("CallAdded(object path, dict properties): path=%s " + "state=%s", path, state); + + switch_profile(u); + } + } + } + +finish: + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +int pa__init(pa_module *m) { + pa_modargs *ma = NULL; + struct userdata *u = NULL; + char *card_name = NULL; + char *profile_name = NULL; + pa_card *card = NULL; + pa_card_profile *profile = NULL; + void *state; + DBusError error; + + pa_assert(m); + + dbus_error_init(&error); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log_error("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->modems_hash = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, + NULL, modems_hash_free_value); + + if (!(card_name = pa_xstrdup(pa_modargs_get_value(ma, "card", NULL)))) { + pa_log_error("Missing card name"); + goto fail; + } + + if (!(profile_name = pa_xstrdup(pa_modargs_get_value(ma, "profile", NULL)))) { + pa_log_error("Missing profile name"); + goto fail; + } + + if (!(card = pa_namereg_get(m->core, card_name, PA_NAMEREG_CARD))) { + pa_log_error("No such card (%s)", card_name); + goto fail; + } + + u->card = card; + + PA_HASHMAP_FOREACH(profile, card->profiles, state) + if (profile && pa_streq(profile->name, profile_name)) { + u->profile = profile; + break; + } + + if (!profile) { + pa_log_error("No such profile (%s) associated with card (%s)", profile_name, card_name); + goto fail; + } + + u->old_profile = card->active_profile; + + if (!(u->bus = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error))) { + pa_log_error("Failed to get system bus connection: %s: %s", error.name, error.message); + goto fail; + } + + if (!dbus_connection_add_filter(pa_dbus_connection_get(u->bus), filter_cb, u, NULL)) { + pa_log_error("Failed to add D-Bus filter function"); + goto fail; + } + + u->filter_added = true; + + if (pa_dbus_add_matches(pa_dbus_connection_get(u->bus), &error, + "type='signal',sender='org.ofono'," + "interface='org.ofono.Manager'," + "path='/'", + NULL) < 0) { + pa_log_error("Unable to subscribe to oFono signals: %s: %s", error.name, error.message); + goto fail; + } + + pa_modargs_free(ma); + pa_xfree(card_name); + pa_xfree(profile_name); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa_xfree(card_name); + pa_xfree(profile_name); + + /* Make sure we don't change anything in pa__done() */ + if (u) { + u->card = NULL; + u->old_profile = NULL; + } + + dbus_error_free(&error); + + pa__done(m); + return -1; +} + +void pa__done(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->modems_hash) + pa_hashmap_free(u->modems_hash); + + if (u->card && u->old_profile) + pa_card_set_profile(u->card, u->old_profile, true); + + if (u->bus) { + + if (u->voicecall_path) { + char *filter = pa_sprintf_malloc("type='signal',sender='org.ofono'," + "interface='org.ofono.VoiceCall'," + "path='%s',member='PropertyChanged'", + u->voicecall_path); + + pa_dbus_remove_matches(pa_dbus_connection_get(u->bus), filter, NULL); + pa_xfree(filter); + pa_xfree(u->voicecall_path); + } + + pa_dbus_remove_matches(pa_dbus_connection_get(u->bus), + "type='signal',sender='org.ofono'," + "interface='org.ofono.Manager'," + "path='/'", + NULL); + + if (u->filter_added) + dbus_connection_remove_filter(pa_dbus_connection_get(u->bus), filter_cb, u); + + pa_dbus_connection_unref(u->bus); + } + + pa_xfree(u); +} -- 1.9.3