/* * Copyright (C) 2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later * * Author: Guido Günther */ #define G_LOG_DOMAIN "CallsEmergencyCallsManager" #include "calls-emergency-calls-manager.h" #include "calls-emergency-call-types.h" #include "calls-origin.h" #include "calls-manager.h" /** * SECTION:calls-emergency-calls-manager * @short_description: Provide a DBus interface for emergency calls * @Title: CallsEmergencyCallsManager * * This tracks emergency call information from the various origins * and makes them available for initiating emergency calls. */ typedef struct _CallsEmergencyCallsManager { CallsDBusEmergencyCallsSkeleton parent; GListModel *origins; } CallsEmergencyCallsManager; static void calls_emergency_calls_iface_init (CallsDBusEmergencyCallsIface *iface); G_DEFINE_TYPE_WITH_CODE (CallsEmergencyCallsManager, calls_emergency_calls_manager, CALLS_DBUS_TYPE_EMERGENCY_CALLS_SKELETON, G_IMPLEMENT_INTERFACE ( CALLS_DBUS_TYPE_EMERGENCY_CALLS, calls_emergency_calls_iface_init)); static void on_emergency_numbers_changed (CallsEmergencyCallsManager *self) { g_signal_emit_by_name (self, "emergency-numbers-changed", 0); } static void on_origins_changed (CallsEmergencyCallsManager *self, guint position, guint removed, guint added) { g_assert (CALLS_IS_EMERGENCY_CALLS_MANAGER (self)); for (int i = 0; i < added; i++) { g_autoptr (CallsOrigin) origin = g_list_model_get_item (self->origins, position - i); g_signal_connect_object (origin, "notify::emergency-numbers", G_CALLBACK (on_emergency_numbers_changed), self, G_CONNECT_SWAPPED); } g_signal_emit_by_name (self, "emergency-numbers-changed", 0); } static gboolean handle_call_emergency_contact (CallsDBusEmergencyCalls *object, GDBusMethodInvocation *invocation, const gchar *arg_id) { CallsEmergencyCallsManager *self = CALLS_EMERGENCY_CALLS_MANAGER (object); g_debug ("Looking for emergency number %s", arg_id); g_return_val_if_fail (CALLS_IS_EMERGENCY_CALLS_MANAGER (self), FALSE); /* Pick the first origin that supports the given emergency number */ for (int i = 0; i < g_list_model_get_n_items (self->origins); i++) { g_autoptr (CallsOrigin) origin = g_list_model_get_item (self->origins, i); g_auto (GStrv) emergency_numbers = NULL; emergency_numbers = calls_origin_get_emergency_numbers (origin); if (!emergency_numbers) continue; for (int j = 0; j < g_strv_length (emergency_numbers); j++) { if (g_strcmp0 (arg_id, emergency_numbers[j]) == 0) { g_debug ("Dialing %s via %s", arg_id, calls_origin_get_name (origin)); calls_origin_dial (origin, arg_id); calls_dbus_emergency_calls_complete_call_emergency_contact (object, invocation); goto done; } } } g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "%s not a known emergency number", arg_id); done: return TRUE; } #define CONTACT_FORMAT "(ssia{sv})" #define CONTACTS_FORMAT "a" CONTACT_FORMAT static void add_emergency_contact (GVariantBuilder *contacts_builder, const char *number, const char *contact, CallsEmergencyContactSource type) { g_variant_builder_open (contacts_builder, G_VARIANT_TYPE (CONTACT_FORMAT)); g_variant_builder_add (contacts_builder, "s", number); g_variant_builder_add (contacts_builder, "s", contact); g_variant_builder_add (contacts_builder, "i", type); /* Currently no hints */ g_variant_builder_add (contacts_builder, "a{sv}", NULL); g_variant_builder_close (contacts_builder); } static gboolean handle_get_emergency_contacts (CallsDBusEmergencyCalls *object, GDBusMethodInvocation *invocation) { GVariant *contacts; GVariantBuilder contacts_builder; CallsEmergencyCallsManager *self = CALLS_EMERGENCY_CALLS_MANAGER (object); g_autoptr (GHashTable) numbers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); g_variant_builder_init (&contacts_builder, G_VARIANT_TYPE (CONTACTS_FORMAT)); for (int i = 0; i < g_list_model_get_n_items (self->origins); i++) { g_autoptr (CallsOrigin) origin = g_list_model_get_item (self->origins, i); g_auto (GStrv) emergency_numbers = NULL; const char *country_code; /* Emergency numbers by location */ country_code = calls_origin_get_network_country_code (origin); emergency_numbers = calls_emergency_call_types_get_numbers_by_country_code (country_code); for (int j = 0; emergency_numbers && j < g_strv_length (emergency_numbers); j++) { g_autofree char *contact = NULL; if (g_hash_table_contains (numbers, emergency_numbers[j])) continue; contact = calls_emergency_call_type_get_name (emergency_numbers[j], country_code); if (contact == NULL) contact = g_strdup (emergency_numbers[j]); add_emergency_contact (&contacts_builder, emergency_numbers[j], contact, CALLS_EMERGENCY_CONTACT_SOURCE_LOCATION); g_hash_table_insert (numbers, g_strdup (emergency_numbers[j]), NULL); } g_clear_pointer (&emergency_numbers, g_strfreev); /* Emergency numbers by origin */ country_code = calls_origin_get_country_code (origin); emergency_numbers = calls_origin_get_emergency_numbers (origin); for (int j = 0; emergency_numbers && j < g_strv_length (emergency_numbers); j++) { g_autofree char *contact = NULL; if (g_hash_table_contains (numbers, emergency_numbers[j])) continue; contact = calls_emergency_call_type_get_name (emergency_numbers[j], country_code); if (contact == NULL) contact = g_strdup (emergency_numbers[j]); add_emergency_contact (&contacts_builder, emergency_numbers[j], contact, /* TODO: allow to query type */ CALLS_EMERGENCY_CONTACT_SOURCE_UNKNOWN); g_hash_table_insert (numbers, g_strdup (emergency_numbers[j]), NULL); } } contacts = g_variant_builder_end (&contacts_builder); calls_dbus_emergency_calls_complete_get_emergency_contacts (object, invocation, contacts); return TRUE; } static void calls_emergency_calls_iface_init (CallsDBusEmergencyCallsIface *iface) { iface->handle_call_emergency_contact = handle_call_emergency_contact; iface->handle_get_emergency_contacts = handle_get_emergency_contacts; } static void calls_emergency_calls_manager_dispose (GObject *object) { CallsEmergencyCallsManager *self = CALLS_EMERGENCY_CALLS_MANAGER (object); g_clear_object (&self->origins); G_OBJECT_CLASS (calls_emergency_calls_manager_parent_class)->dispose (object); } static void calls_emergency_calls_manager_class_init (CallsEmergencyCallsManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = calls_emergency_calls_manager_dispose; } static void calls_emergency_calls_manager_init (CallsEmergencyCallsManager *self) { CallsManager *manager = calls_manager_get_default (); self->origins = g_object_ref (calls_manager_get_origins (manager)); g_signal_connect_object (self->origins, "items-changed", G_CALLBACK (on_origins_changed), self, G_CONNECT_SWAPPED); } CallsEmergencyCallsManager * calls_emergency_calls_manager_new (void) { return g_object_new (CALLS_TYPE_EMERGENCY_CALLS_MANAGER, NULL); }