Files
ofono/ofono/plugins/sailfish_manager/sailfish_manager.c
Slava Monich cb6b24d950 [ofono] Store one-time data SIM selection. JB#48462
This way, automatic choice survives a reboot.
2020-02-13 18:53:32 +03:00

1544 lines
41 KiB
C

/*
* oFono - Open Source Telephony
*
* Copyright (C) 2017-2020 Jolla Ltd.
* Copyright (C) 2019-2020 Open Mobile Platform LLC.
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <gutil_log.h>
#include <gutil_strv.h>
#include <gutil_macros.h>
#include <string.h>
#include <ofono/storage.h>
#include <ofono/watch.h>
#include "src/ofono.h"
#include "src/storage.h"
#include <sailfish_manager.h>
#include <sailfish_cell_info.h>
#include "sailfish_manager_dbus.h"
#include "sailfish_cell_info_dbus.h"
#include "sailfish_sim_info.h"
/* How long we wait for all drivers to register (number of idle loops) */
#define SF_INIT_IDLE_COUNT (5)
enum ofono_watch_events {
WATCH_EVENT_MODEM,
WATCH_EVENT_ONLINE,
WATCH_EVENT_IMSI,
WATCH_EVENT_COUNT
};
enum sim_auto_select {
SIM_AUTO_SELECT_OFF,
SIM_AUTO_SELECT_ON,
SIM_AUTO_SELECT_ONCE
};
struct sailfish_manager_priv {
struct sailfish_manager pub; /* Public part */
struct sailfish_slot_driver_reg *drivers;
struct sailfish_manager_dbus *dbus;
struct sailfish_slot_priv *voice_slot;
struct sailfish_slot_priv *data_slot;
struct sailfish_slot_priv *mms_slot;
sailfish_slot_ptr *slots;
enum sim_auto_select auto_data_sim;
gboolean auto_data_sim_done;
int slot_count;
guint init_countdown;
guint init_id;
char *default_voice_imsi;
char *default_data_imsi;
char *mms_imsi;
GKeyFile *storage;
GHashTable *errors;
};
struct sailfish_slot_driver_reg {
struct sailfish_slot_driver_reg *next;
const struct sailfish_slot_driver *driver;
struct sailfish_manager_priv *plugin;
struct sailfish_slot_manager *manager;
guint init_id;
};
struct sailfish_slot_manager {
const struct sailfish_slot_driver *driver;
struct sailfish_manager_priv *plugin;
struct sailfish_slot_manager_impl *impl;
struct sailfish_slot_priv *slots;
gboolean started;
guint start_id;
};
struct sailfish_slot_priv {
struct sailfish_slot pub;
struct sailfish_slot_priv *next;
struct sailfish_slot_manager *manager;
struct sailfish_slot_impl *impl;
struct ofono_watch *watch;
struct sailfish_sim_info *siminfo;
struct sailfish_sim_info_dbus *siminfo_dbus;
struct sailfish_cell_info *cellinfo;
struct sailfish_cell_info_dbus *cellinfo_dbus;
enum sailfish_sim_state sim_state;
enum sailfish_slot_flags flags;
gulong watch_event_id[WATCH_EVENT_COUNT];
char *imei;
char *imeisv;
gboolean enabled_changed;
GHashTable *errors;
int index;
};
/* Read-only config */
#define SF_CONFIG_FILE "main.conf"
#define SF_CONFIG_GROUP "ModemManager"
#define SF_CONFIG_KEY_AUTO_DATA_SIM "AutoSelectDataSim"
/* "ril" is used for historical reasons */
#define SF_STORE "ril"
#define SF_STORE_GROUP "Settings"
#define SF_STORE_ENABLED_SLOTS "EnabledSlots"
#define SF_STORE_DEFAULT_VOICE_SIM "DefaultVoiceSim"
#define SF_STORE_DEFAULT_DATA_SIM "DefaultDataSim"
#define SF_STORE_SLOTS_SEP ","
#define SF_STORE_AUTO_DATA_SIM_DONE "AutoSelectDataSimDone"
/* The file where error statistics is stored. Again "rilerror" is historical */
#define SF_ERROR_STORAGE "rilerror" /* File name */
#define SF_ERROR_COMMON_SECTION "common" /* Modem independent section */
/* Path always starts with a slash, skip it */
#define sailfish_slot_debug_prefix(s) ((s)->pub.path + 1)
static int sailfish_manager_update_modem_paths(struct sailfish_manager_priv *);
static gboolean sailfish_manager_update_ready(struct sailfish_manager_priv *p);
static inline struct sailfish_manager_priv *sailfish_manager_priv_cast
(struct sailfish_manager *m)
{
return G_CAST(m, struct sailfish_manager_priv, pub);
}
static inline struct sailfish_slot_priv *sailfish_slot_priv_cast
(struct sailfish_slot *s)
{
return G_CAST(s, struct sailfish_slot_priv, pub);
}
static inline const struct sailfish_slot_priv *sailfish_slot_priv_cast_const
(const struct sailfish_slot *s)
{
return G_CAST(s, struct sailfish_slot_priv, pub);
}
static inline void sailfish_slot_set_data_role(struct sailfish_slot_priv *s,
enum sailfish_data_role role)
{
const struct sailfish_slot_driver *d = s->manager->driver;
if (d->slot_set_data_role) {
d->slot_set_data_role(s->impl, role);
}
}
static gboolean sailfish_config_get_enum(GKeyFile *file, const char *group,
const char *key, int *result,
const char *name, int value, ...)
{
char *str = g_key_file_get_string(file, group, key, NULL);
if (str) {
/*
* Some people are thinking that # is a comment
* anywhere on the line, not just at the beginning
*/
char *comment = strchr(str, '#');
if (comment) *comment = 0;
g_strstrip(str);
if (strcasecmp(str, name)) {
va_list args;
va_start(args, value);
while ((name = va_arg(args, char*)) != NULL) {
value = va_arg(args, int);
if (!strcasecmp(str, name)) {
break;
}
}
va_end(args);
}
if (!name) {
ofono_error("Invalid %s config value (%s)", key, str);
}
g_free(str);
if (name) {
if (result) {
*result = value;
}
return TRUE;
}
}
return FALSE;
}
/* Update modem paths and emit D-Bus signal if necessary */
static void sailfish_manager_update_modem_paths_full
(struct sailfish_manager_priv *p)
{
sailfish_manager_dbus_signal(p->dbus,
sailfish_manager_update_modem_paths(p));
}
/*
* sailfish_manager_foreach_driver() and sailfish_manager_foreach_slot()
* terminate the loop and return TRUE if the callback returns TRUE. If all
* callbacks return FALSE, they returns FALSE. It there are no drivers/slots,
* they return FALSE too.
*/
#define SF_LOOP_CONTINUE (FALSE)
#define SF_LOOP_DONE (TRUE)
static gboolean sailfish_manager_foreach_driver(struct sailfish_manager_priv *p,
gboolean (*fn)(struct sailfish_slot_driver_reg *r, void *user_data),
void *user_data)
{
struct sailfish_slot_driver_reg *r = p->drivers;
gboolean done = FALSE;
while (r && !done) {
struct sailfish_slot_driver_reg *rnext = r->next;
/* The callback returns TRUE to terminate the loop */
done = fn(r, user_data);
r = rnext;
}
return done;
}
static gboolean sailfish_manager_foreach_slot
(struct sailfish_manager_priv *p,
gboolean (*fn)(struct sailfish_slot_priv *s, void *user_data),
void *user_data)
{
struct sailfish_slot_driver_reg *r = p->drivers;
gboolean done = FALSE;
while (r && !done) {
struct sailfish_slot_manager *m = r->manager;
struct sailfish_slot_driver_reg *rnext = r->next;
if (m) {
struct sailfish_slot_priv *s = m->slots;
while (s) {
struct sailfish_slot_priv *snext = s->next;
/* The callback returns TRUE to terminate
* the loop */
if (fn(s, user_data)) {
done = TRUE;
break;
}
s = snext;
}
}
r = rnext;
}
return done;
}
static void sailfish_manager_slot_update_cell_info_dbus
(struct sailfish_slot_priv *s)
{
struct ofono_modem *modem = s->watch->modem;
if (modem && s->cellinfo) {
if (!s->cellinfo_dbus) {
s->cellinfo_dbus = sailfish_cell_info_dbus_new(modem,
s->cellinfo);
}
} else {
if (s->cellinfo_dbus) {
sailfish_cell_info_dbus_free(s->cellinfo_dbus);
s->cellinfo_dbus = NULL;
}
}
}
static void sailfish_manager_slot_modem_changed(struct ofono_watch *w,
void *user_data)
{
struct sailfish_slot_priv *s = user_data;
struct sailfish_manager_priv *p = s->manager->plugin;
sailfish_manager_slot_update_cell_info_dbus(s);
sailfish_manager_update_modem_paths_full(p);
sailfish_manager_update_ready(p);
}
static void sailfish_manager_slot_imsi_changed(struct ofono_watch *w,
void *user_data)
{
struct sailfish_slot_priv *slot = user_data;
struct sailfish_manager_priv *plugin = slot->manager->plugin;
struct sailfish_slot_priv *voice_slot = plugin->voice_slot;
struct sailfish_slot_priv *data_slot = plugin->data_slot;
int signal_mask;
/*
* We want the first slot to be selected by default.
* However, things may become available in pretty much
* any order, so reset the slot pointers to NULL and let
* sailfish_manager_update_modem_paths() to pick them again.
*/
plugin->voice_slot = NULL;
plugin->data_slot = NULL;
plugin->pub.default_voice_path = NULL;
plugin->pub.default_data_path = NULL;
signal_mask = sailfish_manager_update_modem_paths(plugin);
if (voice_slot != plugin->voice_slot) {
if (!plugin->voice_slot) {
DBG("No default voice SIM");
}
signal_mask |= SAILFISH_MANAGER_SIGNAL_VOICE_PATH;
}
if (data_slot != plugin->data_slot) {
if (!plugin->data_slot) {
DBG("No default data SIM");
}
signal_mask |= SAILFISH_MANAGER_SIGNAL_DATA_PATH;
}
sailfish_manager_dbus_signal(plugin->dbus, signal_mask);
}
static gboolean sailfish_manager_count_slot(struct sailfish_slot_priv *s,
void *user_data)
{
(*((int *)user_data))++;
return SF_LOOP_CONTINUE;
}
static gboolean sailfish_manager_index_slot(struct sailfish_slot_priv *s,
void *user_data)
{
struct sailfish_manager_priv *p = user_data;
s->index = p->slot_count;
p->slots[p->slot_count++] = &s->pub;
return SF_LOOP_CONTINUE;
}
static void sailfish_manager_reindex_slots(struct sailfish_manager_priv *p)
{
int count = 0;
sailfish_manager_foreach_slot(p, sailfish_manager_count_slot, &count);
g_free(p->slots);
p->pub.slots = p->slots = g_new0(sailfish_slot_ptr, count + 1);
/* p->slot_count is the index for sailfish_manager_index_slot */
p->slot_count = 0;
sailfish_manager_foreach_slot(p, sailfish_manager_index_slot, p);
p->slots[p->slot_count] = NULL;
GASSERT(p->slot_count == count);
}
static gboolean sailfish_manager_check_slot_name(struct sailfish_slot_priv *s,
void *path)
{
return strcmp(s->pub.path, path) ? SF_LOOP_CONTINUE : SF_LOOP_DONE;
}
struct sailfish_slot *sailfish_manager_slot_add
(struct sailfish_slot_manager *m, struct sailfish_slot_impl *impl,
const char *path, enum ofono_radio_access_mode techs,
const char *imei, const char *imeisv,
enum sailfish_sim_state sim_state)
{
return sailfish_manager_slot_add2(m, impl, path, techs, imei, imeisv,
sim_state, SAILFISH_SLOT_NO_FLAGS);
}
struct sailfish_slot *sailfish_manager_slot_add2
(struct sailfish_slot_manager *m, struct sailfish_slot_impl *impl,
const char *path, enum ofono_radio_access_mode techs,
const char *imei, const char *imeisv,
enum sailfish_sim_state sim_state,
enum sailfish_slot_flags flags)
{
/* Only accept these calls when we are starting! We have been
* assuming all along that the number of slots is known right
* from startup. Perhaps it wasn't a super bright idea because
* there are USB modems which can appear (and disappear) pretty
* much at any time. This has to be dealt with somehow at some
* point but for now let's leave it as is. */
if (path && m && !m->started && !sailfish_manager_foreach_slot
(m->plugin, sailfish_manager_check_slot_name,
(char*)path)) {
char *enabled_slots;
struct sailfish_manager_priv *p = m->plugin;
struct sailfish_slot_priv *s =
g_slice_new0(struct sailfish_slot_priv);
DBG("%s", path);
s->impl = impl;
s->manager = m;
s->sim_state = sim_state;
s->flags = flags;
s->watch = ofono_watch_new(path);
s->siminfo = sailfish_sim_info_new(path);
s->siminfo_dbus = sailfish_sim_info_dbus_new(s->siminfo);
s->pub.path = s->watch->path;
s->pub.imei = s->imei = g_strdup(imei);
s->pub.imeisv = s->imeisv = g_strdup(imeisv);
s->pub.sim_present = (sim_state == SAILFISH_SIM_STATE_PRESENT);
/* Check if it's enabled */
enabled_slots = g_key_file_get_string(p->storage,
SF_STORE_GROUP, SF_STORE_ENABLED_SLOTS, NULL);
if (enabled_slots) {
char **strv = g_strsplit(enabled_slots,
SF_STORE_SLOTS_SEP, 0);
DBG("Enabled slots: %s", enabled_slots);
s->pub.enabled = gutil_strv_contains(strv, path);
g_strfreev(strv);
g_free(enabled_slots);
} else {
/* All slots are enabled by default */
s->pub.enabled = TRUE;
}
/* Add it to the list */
if (!m->slots) {
/* The first one */
m->slots = s;
} else if (strcmp(m->slots->pub.path, path) > 0) {
/* This one becomes the head of the list */
s->next = m->slots;
m->slots = s;
} else {
/* Need to do some sorting */
struct sailfish_slot_priv *prev = m->slots;
struct sailfish_slot_priv *slot = m->slots->next;
while (slot && strcmp(slot->pub.path, path) < 0) {
prev = slot;
slot = prev->next;
}
s->next = prev->next;
prev->next = s;
}
sailfish_manager_reindex_slots(m->plugin);
/* Register for events */
s->watch_event_id[WATCH_EVENT_MODEM] =
ofono_watch_add_modem_changed_handler(s->watch,
sailfish_manager_slot_modem_changed, s);
s->watch_event_id[WATCH_EVENT_ONLINE] =
ofono_watch_add_online_changed_handler(s->watch,
sailfish_manager_slot_modem_changed, s);
s->watch_event_id[WATCH_EVENT_IMSI] =
ofono_watch_add_imsi_changed_handler(s->watch,
sailfish_manager_slot_imsi_changed, s);
return &s->pub;
} else {
ofono_error("Refusing to register slot %s", path);
}
return NULL;
}
static void sailfish_slot_free(struct sailfish_slot_priv *s)
{
struct sailfish_slot_manager *m = s->manager;
struct sailfish_manager_priv *p = m->plugin;
if (s->impl) {
const struct sailfish_slot_driver *d = m->driver;
if (d->slot_free) {
d->slot_free(s->impl);
s->impl = NULL;
}
}
if (s->errors) {
g_hash_table_destroy(s->errors);
}
sailfish_sim_info_unref(s->siminfo);
sailfish_sim_info_dbus_free(s->siminfo_dbus);
sailfish_cell_info_dbus_free(s->cellinfo_dbus);
sailfish_cell_info_unref(s->cellinfo);
ofono_watch_remove_all_handlers(s->watch, s->watch_event_id);
ofono_watch_unref(s->watch);
g_free(s->imei);
g_free(s->imeisv);
s->next = NULL;
s->manager = NULL;
g_slice_free(struct sailfish_slot_priv, s);
sailfish_manager_reindex_slots(p);
}
void sailfish_manager_set_cell_info(struct sailfish_slot *s,
struct sailfish_cell_info *info)
{
if (s) {
struct sailfish_slot_priv *slot = sailfish_slot_priv_cast(s);
if (slot->cellinfo != info) {
sailfish_cell_info_dbus_free(slot->cellinfo_dbus);
sailfish_cell_info_unref(slot->cellinfo);
slot->cellinfo = sailfish_cell_info_ref(info);
slot->cellinfo_dbus = NULL;
sailfish_manager_slot_update_cell_info_dbus(slot);
}
}
}
static gboolean sailfish_manager_update_dbus_block_proc
(struct sailfish_slot_driver_reg *r, void *data)
{
enum sailfish_manager_dbus_block *block = data;
struct sailfish_slot_manager *m;
struct sailfish_slot_priv *s;
if (r->init_id) {
/* Driver is being initialized */
(*block) |= SAILFISH_MANAGER_DBUS_BLOCK_ALL;
return SF_LOOP_DONE;
}
m = r->manager;
if (!m) {
return SF_LOOP_CONTINUE;
}
if (!m->started) {
/* Slots are being initialized */
(*block) |= SAILFISH_MANAGER_DBUS_BLOCK_ALL;
return SF_LOOP_DONE;
}
for (s = m->slots; s && s->imei; s = s->next);
if (s) {
/* IMEI is not available (yet) */
(*block) |= SAILFISH_MANAGER_DBUS_BLOCK_IMEI;
}
return SF_LOOP_CONTINUE;
}
static void sailfish_manager_update_dbus_block(struct sailfish_manager_priv *p)
{
enum sailfish_manager_dbus_block block =
SAILFISH_MANAGER_DBUS_BLOCK_NONE;
if (p->init_countdown) {
/* Plugin is being initialized */
block |= SAILFISH_MANAGER_DBUS_BLOCK_ALL;
} else {
sailfish_manager_foreach_driver(p,
sailfish_manager_update_dbus_block_proc, &block);
}
sailfish_manager_dbus_set_block(p->dbus, block);
}
static void sailfish_manager_set_config_string
(struct sailfish_manager_priv *p, const char *key,
const char *value)
{
if (value) {
g_key_file_set_string(p->storage, SF_STORE_GROUP, key, value);
} else {
g_key_file_remove_key(p->storage, SF_STORE_GROUP, key, NULL);
}
storage_sync(NULL, SF_STORE, p->storage);
}
struct sailfish_manager_slot_imsi_data {
struct sailfish_slot_priv *slot;
const char *imsi;
};
static gboolean sailfish_manager_find_slot_imsi_proc
(struct sailfish_slot_priv *s, void *user_data)
{
struct sailfish_manager_slot_imsi_data *data = user_data;
const char *slot_imsi = s->watch->imsi;
if (slot_imsi && !strcmp(slot_imsi, data->imsi)) {
data->slot = s;
return SF_LOOP_DONE;
} else {
return SF_LOOP_CONTINUE;
}
}
struct sailfish_manager_any_slot_data {
struct sailfish_slot_priv *slot;
};
static gboolean sailfish_manager_find_any_slot_proc
(struct sailfish_slot_priv *s, void *user_data)
{
struct sailfish_manager_any_slot_data *data = user_data;
const char *slot_imsi = s->watch->imsi;
if (slot_imsi) {
data->slot = s;
return SF_LOOP_DONE;
} else {
return SF_LOOP_CONTINUE;
}
}
static struct sailfish_slot_priv *sailfish_manager_find_slot_imsi
(struct sailfish_manager_priv *p,
const char *imsi)
{
if (imsi) {
/* We are looking for the specific sim */
struct sailfish_manager_slot_imsi_data data;
memset(&data, 0, sizeof(data));
data.imsi = imsi;
sailfish_manager_foreach_slot(p,
sailfish_manager_find_slot_imsi_proc, &data);
return data.slot;
} else {
/* We are looking for any slot with a sim */
struct sailfish_manager_any_slot_data data;
memset(&data, 0, sizeof(data));
sailfish_manager_foreach_slot(p,
sailfish_manager_find_any_slot_proc, &data);
return data.slot;
}
}
static gboolean sailfish_manager_all_sims_are_initialized_proc
(struct sailfish_slot_priv *s, void *user_data)
{
if (s->pub.sim_present && s->pub.enabled && !s->watch->imsi) {
*((gboolean*)user_data) = FALSE;
return SF_LOOP_DONE;
} else {
return SF_LOOP_CONTINUE;
}
}
static gboolean sailfish_manager_all_sims_are_initialized
(struct sailfish_manager_priv *p)
{
gboolean result = TRUE;
sailfish_manager_foreach_slot(p,
sailfish_manager_all_sims_are_initialized_proc, &result);
return result;
}
/* Returns the event mask to be passed to sailfish_manager_dbus_signal.
* The caller has a chance to OR it with other bits */
static int sailfish_manager_update_modem_paths(struct sailfish_manager_priv *p)
{
int mask = 0;
struct sailfish_slot_priv *slot = NULL;
struct sailfish_slot_priv *mms_slot = NULL;
struct sailfish_slot_priv *old_data_slot = NULL;
struct sailfish_slot_priv *new_data_slot = NULL;
/* Voice */
if (p->default_voice_imsi) {
slot = sailfish_manager_find_slot_imsi(p,
p->default_voice_imsi);
} else if (p->voice_slot) {
/* Make sure that the slot is enabled and SIM is in */
slot = sailfish_manager_find_slot_imsi(p,
p->voice_slot->watch->imsi);
}
/*
* If there's no default voice SIM, we will find any SIM instead.
* One should always be able to make and receive a phone call
* if there's a working SIM in the phone. However if the
* previously selected voice SIM is inserted, we will switch
* back to it.
*
* A similar behavior can be configured for data SIM too.
*/
if (!slot) {
slot = sailfish_manager_find_slot_imsi(p, NULL);
}
if (p->voice_slot != slot) {
mask |= SAILFISH_MANAGER_SIGNAL_VOICE_PATH;
p->voice_slot = slot;
if (slot) {
const char *path = slot->pub.path;
DBG("Default voice SIM at %s", path);
p->pub.default_voice_path = path;
} else {
DBG("No default voice SIM");
p->pub.default_voice_path = NULL;
}
}
/* Data */
if (p->default_data_imsi) {
slot = sailfish_manager_find_slot_imsi(p,
p->default_data_imsi);
} else if (p->slot_count < 2) {
if (p->data_slot) {
/* Make sure that the slot is enabled and SIM is in */
slot = sailfish_manager_find_slot_imsi(p,
p->data_slot->watch->imsi);
} else {
/* Check if anything is available */
slot = sailfish_manager_find_slot_imsi(p, NULL);
}
} else {
slot = NULL;
}
/* Check if we need to auto-select data SIM (always or once) */
if (!slot && (p->auto_data_sim == SIM_AUTO_SELECT_ON ||
(p->auto_data_sim == SIM_AUTO_SELECT_ONCE &&
!p->auto_data_sim_done))) {
/*
* To actually make a selection we need all present SIMs
* to be initialized. Otherwise we may end up endlessly
* switching data SIMs back and forth.
*/
if (sailfish_manager_all_sims_are_initialized(p)) {
slot = sailfish_manager_find_slot_imsi(p, NULL);
if (slot && slot->watch->imsi && slot->watch->online &&
p->auto_data_sim == SIM_AUTO_SELECT_ONCE) {
const char *imsi = slot->watch->imsi;
/*
* Data SIM only needs to be auto-selected
* once and it's done. Write that down.
*/
DBG("Default data sim set to %s once", imsi);
p->auto_data_sim_done = TRUE;
g_key_file_set_boolean(p->storage,
SF_STORE_GROUP,
SF_STORE_AUTO_DATA_SIM_DONE,
p->auto_data_sim_done);
g_free(p->default_data_imsi);
p->pub.default_data_imsi =
p->default_data_imsi = g_strdup(imsi);
g_key_file_set_string(p->storage,
SF_STORE_GROUP,
SF_STORE_DEFAULT_DATA_SIM,
imsi);
storage_sync(NULL, SF_STORE, p->storage);
sailfish_manager_dbus_signal(p->dbus,
SAILFISH_MANAGER_SIGNAL_DATA_IMSI);
}
} else {
DBG("Skipping auto-selection of data SIM");
}
}
if (slot && !slot->watch->online) {
slot = NULL;
}
if (p->mms_imsi) {
mms_slot = sailfish_manager_find_slot_imsi(p, p->mms_imsi);
}
if (mms_slot && (mms_slot != slot ||
(slot->flags & SAILFISH_SLOT_SINGLE_CONTEXT))) {
/*
* Reset default data SIM if
* a) another SIM is temporarily selected for MMS; or
* b) this slot can't have more than one context active.
*/
slot = NULL;
}
/* Are we actually switching data SIMs? */
old_data_slot = p->mms_slot ? p->mms_slot : p->data_slot;
new_data_slot = mms_slot ? mms_slot : slot;
if (p->data_slot != slot) {
mask |= SAILFISH_MANAGER_SIGNAL_DATA_PATH;
p->data_slot = slot;
if (slot) {
const char *path = slot->pub.path;
DBG("Default data SIM at %s", path);
p->pub.default_data_path = path;
} else {
DBG("No default data SIM");
p->pub.default_data_path = NULL;
}
}
if (p->mms_slot != mms_slot) {
mask |= SAILFISH_MANAGER_SIGNAL_MMS_PATH;
p->mms_slot = mms_slot;
if (mms_slot) {
const char *path = mms_slot->pub.path;
DBG("MMS data SIM at %s", path);
p->pub.mms_path = path;
} else {
DBG("No MMS data SIM");
p->pub.mms_path = NULL;
}
}
if (old_data_slot != new_data_slot) {
/* Yes we are switching data SIMs */
if (old_data_slot) {
sailfish_slot_set_data_role(old_data_slot,
SAILFISH_DATA_ROLE_NONE);
}
if (new_data_slot) {
sailfish_slot_set_data_role(new_data_slot,
(new_data_slot == p->data_slot) ?
SAILFISH_DATA_ROLE_INTERNET :
SAILFISH_DATA_ROLE_MMS);
}
}
return mask;
}
static gboolean sailfish_manager_update_ready_driver_proc
(struct sailfish_slot_driver_reg *r, void *unused)
{
struct sailfish_slot_manager *m = r->manager;
if (!m || m->started) {
/* This one is either missing or ready */
return SF_LOOP_CONTINUE;
} else {
/* This one is not */
return SF_LOOP_DONE;
}
}
static gboolean sailfish_manager_update_ready_slot_proc
(struct sailfish_slot_priv *s, void *unused)
{
if (s->imei && s->sim_state != SAILFISH_SIM_STATE_UNKNOWN) {
/* This one is ready */
return SF_LOOP_CONTINUE;
} else {
/* This one is not */
return SF_LOOP_DONE;
}
}
static gboolean sailfish_manager_update_ready(struct sailfish_manager_priv *p)
{
/*
* sailfish_manager_foreach_driver and sailfish_manager_foreach_slot
* return FALSE if either all callbacks returned SF_LOOP_CONTINUE or
* there are no drivers/slots. In either case we are ready. */
const gboolean ready =
!sailfish_manager_foreach_driver
(p,sailfish_manager_update_ready_driver_proc, NULL) &&
!sailfish_manager_foreach_slot
(p, sailfish_manager_update_ready_slot_proc, NULL);
if (p->pub.ready != ready) {
p->pub.ready = ready;
sailfish_manager_update_dbus_block(p);
DBG("%sready", ready ? "" : "not ");
sailfish_manager_dbus_signal(p->dbus,
SAILFISH_MANAGER_SIGNAL_READY);
return TRUE;
} else {
return FALSE;
}
}
void sailfish_manager_imei_obtained(struct sailfish_slot *s, const char *imei)
{
if (s) {
struct sailfish_slot_priv *slot = sailfish_slot_priv_cast(s);
/* We assume that IMEI never changes */
GASSERT(imei);
GASSERT(!slot->imei || !g_strcmp0(slot->imei, imei));
g_free(slot->imei); /* Just in case */
slot->pub.imei = slot->imei = g_strdup(imei);
sailfish_manager_update_ready(slot->manager->plugin);
}
}
void sailfish_manager_imeisv_obtained(struct sailfish_slot *s,
const char *imeisv)
{
if (s) {
struct sailfish_slot_priv *slot = sailfish_slot_priv_cast(s);
/* We assume that IMEISV never changes */
GASSERT(imeisv);
GASSERT(!slot->imeisv || !g_strcmp0(slot->imeisv, imeisv));
g_free(slot->imeisv); /* Just in case */
slot->pub.imeisv = slot->imeisv = g_strdup(imeisv);
sailfish_manager_update_ready(slot->manager->plugin);
}
}
void sailfish_manager_set_sim_state(struct sailfish_slot *s,
enum sailfish_sim_state state)
{
if (s) {
struct sailfish_slot_priv *slot = sailfish_slot_priv_cast(s);
struct sailfish_manager_priv *p = slot->manager->plugin;
const gboolean present = (state == SAILFISH_SIM_STATE_PRESENT);
if (slot->pub.sim_present != present) {
slot->pub.sim_present = present;
sailfish_manager_dbus_signal_sim(p->dbus,
slot->index, present);
sailfish_manager_update_modem_paths_full(p);
}
if (slot->sim_state != state) {
slot->sim_state = state;
sailfish_manager_update_ready(p);
}
}
}
static gboolean sailfish_manager_update_enabled_slot
(struct sailfish_slot_priv *s, void *unused)
{
if (s->pub.enabled && s->enabled_changed) {
const struct sailfish_slot_driver *d = s->manager->driver;
DBG("%s enabled", sailfish_slot_debug_prefix(s));
s->enabled_changed = TRUE;
if (d->slot_enabled_changed) {
d->slot_enabled_changed(s->impl);
}
}
return SF_LOOP_CONTINUE;
}
static gboolean sailfish_manager_update_disabled_slot
(struct sailfish_slot_priv *s, void *unused)
{
if (!s->pub.enabled && s->enabled_changed) {
struct sailfish_slot_manager *m = s->manager;
const struct sailfish_slot_driver *d = m->driver;
DBG("%s disabled", sailfish_slot_debug_prefix(s));
s->enabled_changed = FALSE;
if (d->slot_enabled_changed) {
d->slot_enabled_changed(s->impl);
}
sailfish_manager_update_modem_paths_full(m->plugin);
}
return SF_LOOP_CONTINUE;
}
static void sailfish_manager_update_slots(struct sailfish_manager_priv *p)
{
sailfish_manager_foreach_slot(p, sailfish_manager_update_disabled_slot,
NULL);
sailfish_manager_foreach_slot(p, sailfish_manager_update_enabled_slot,
NULL);
sailfish_manager_update_modem_paths_full(p);
}
static gboolean sailfish_manager_enabled_slots_proc
(struct sailfish_slot_priv *slot, void *user_data)
{
if (slot->pub.enabled) {
char ***list = user_data;
*list = gutil_strv_add(*list, slot->pub.path);
}
return SF_LOOP_CONTINUE;
}
struct sailfish_manager_set_enabled_slots_data {
gchar * const * enabled;
gboolean all_enabled;
gboolean changed;
};
static gboolean sailfish_manager_set_enabled_slots_proc
(struct sailfish_slot_priv *slot, void *user_data)
{
struct sailfish_manager_set_enabled_slots_data *data = user_data;
struct sailfish_slot *s = &slot->pub;
const gboolean was_enabled = s->enabled;
s->enabled = gutil_strv_contains(data->enabled, s->path);
if ((was_enabled && !s->enabled) || (!was_enabled && s->enabled)) {
slot->enabled_changed = TRUE;
data->changed = TRUE;
}
if (!s->enabled) {
data->all_enabled = FALSE;
}
return SF_LOOP_CONTINUE;
}
static void sailfish_manager_set_enabled_slots(struct sailfish_manager *m,
gchar **slots)
{
struct sailfish_manager_priv *p = sailfish_manager_priv_cast(m);
struct sailfish_manager_set_enabled_slots_data data;
data.enabled = slots;
data.changed = FALSE;
data.all_enabled = TRUE;
sailfish_manager_foreach_slot(p,
sailfish_manager_set_enabled_slots_proc, &data);
if (data.changed) {
char **new_slots = NULL;
sailfish_manager_foreach_slot(p,
sailfish_manager_enabled_slots_proc, &new_slots);
/* Save the new config value. If it exactly matches the list
* of available modems, delete the setting because that's the
* default behavior. */
if (data.all_enabled) {
sailfish_manager_set_config_string(p,
SF_STORE_ENABLED_SLOTS, NULL);
} else {
const char *value;
char *tmp;
if (new_slots) {
tmp = g_strjoinv(SF_STORE_SLOTS_SEP, new_slots);
value = tmp;
} else {
tmp = NULL;
value = "";
}
sailfish_manager_set_config_string(p,
SF_STORE_ENABLED_SLOTS, value);
g_free(tmp);
}
g_strfreev(new_slots);
sailfish_manager_dbus_signal(p->dbus,
SAILFISH_MANAGER_SIGNAL_ENABLED_SLOTS);
/* Add and remove modems */
sailfish_manager_update_slots(p);
}
}
static void sailfish_manager_set_default_voice_imsi(struct sailfish_manager *m,
const char *imsi)
{
struct sailfish_manager_priv *p = sailfish_manager_priv_cast(m);
if (g_strcmp0(p->default_voice_imsi, imsi)) {
DBG("Default voice sim set to %s", imsi ? imsi : "(auto)");
g_free(p->default_voice_imsi);
m->default_voice_imsi =
p->default_voice_imsi = g_strdup(imsi);
sailfish_manager_set_config_string(p,
SF_STORE_DEFAULT_VOICE_SIM, imsi);
sailfish_manager_dbus_signal(p->dbus,
SAILFISH_MANAGER_SIGNAL_VOICE_IMSI |
sailfish_manager_update_modem_paths(p));
}
}
static void sailfish_manager_set_default_data_imsi(struct sailfish_manager *m,
const char *imsi)
{
struct sailfish_manager_priv *p = sailfish_manager_priv_cast(m);
if (g_strcmp0(p->default_data_imsi, imsi)) {
DBG("Default data sim set to %s", imsi ? imsi : "(auto)");
g_free(p->default_data_imsi);
m->default_data_imsi =
p->default_data_imsi = g_strdup(imsi);
sailfish_manager_set_config_string(p,
SF_STORE_DEFAULT_DATA_SIM, imsi);
sailfish_manager_dbus_signal(p->dbus,
SAILFISH_MANAGER_SIGNAL_DATA_IMSI |
sailfish_manager_update_modem_paths(p));
}
}
static gboolean sailfish_manager_set_mms_imsi(struct sailfish_manager *m,
const char *imsi)
{
struct sailfish_manager_priv *p = sailfish_manager_priv_cast(m);
if (imsi && imsi[0]) {
if (g_strcmp0(p->mms_imsi, imsi)) {
if (sailfish_manager_find_slot_imsi(p, imsi)) {
DBG("MMS sim %s", imsi);
g_free(p->mms_imsi);
m->mms_imsi = p->mms_imsi = g_strdup(imsi);
sailfish_manager_dbus_signal(p->dbus,
SAILFISH_MANAGER_SIGNAL_MMS_IMSI |
sailfish_manager_update_modem_paths(p));
} else {
DBG("IMSI not found: %s", imsi);
return FALSE;
}
}
} else {
if (p->mms_imsi) {
DBG("No MMS sim");
g_free(p->mms_imsi);
m->mms_imsi = p->mms_imsi = NULL;
sailfish_manager_dbus_signal(p->dbus,
SAILFISH_MANAGER_SIGNAL_MMS_IMSI |
sailfish_manager_update_modem_paths(p));
}
}
return TRUE;
}
static GHashTable *sailfish_manager_inc_error_count(GHashTable *errors,
const char *group, const char *key)
{
GKeyFile *storage = storage_open(NULL, SF_ERROR_STORAGE);
/* Update life-time statistics */
if (storage) {
g_key_file_set_integer(storage, group, key,
g_key_file_get_integer(storage, group, key, NULL) + 1);
storage_close(NULL, SF_ERROR_STORAGE, storage, TRUE);
}
/* Update run-time error counts. The key is the error id which
* is always a static string */
if (!errors) {
errors = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
}
g_hash_table_insert(errors, g_strdup(key), GINT_TO_POINTER(
GPOINTER_TO_INT(g_hash_table_lookup(errors, key)) + 1));
return errors;
}
void sailfish_manager_error(struct sailfish_slot_manager *m, const char *key,
const char *message)
{
if (m) {
struct sailfish_manager_priv *p = m->plugin;
p->errors = sailfish_manager_inc_error_count(p->errors,
SF_ERROR_COMMON_SECTION, key);
sailfish_manager_dbus_signal_error(p->dbus, key, message);
}
}
void sailfish_manager_slot_error(struct sailfish_slot *s, const char *key,
const char *msg)
{
if (s) {
struct sailfish_slot_priv *priv = sailfish_slot_priv_cast(s);
/* slot->path always starts with a slash, skip it */
const char *section = s->path + 1;
priv->errors = sailfish_manager_inc_error_count(priv->errors,
section, key);
sailfish_manager_dbus_signal_modem_error
(priv->manager->plugin->dbus, priv->index, key, msg);
}
}
static GHashTable *sailfish_manager_get_errors(struct sailfish_manager *m)
{
return sailfish_manager_priv_cast(m)->errors;
}
static GHashTable *sailfish_manager_get_slot_errors
(const struct sailfish_slot *s)
{
return sailfish_slot_priv_cast_const(s)->errors;
}
static void sailfish_slot_manager_has_started(struct sailfish_slot_manager *m)
{
if (!m->started) {
DBG("%s", m->driver->name);
m->started = TRUE;
if (!sailfish_manager_update_ready(m->plugin)) {
sailfish_manager_update_dbus_block(m->plugin);
}
}
}
void sailfish_slot_manager_started(struct sailfish_slot_manager *m)
{
DBG("%s", m->driver->name);
m->start_id = 0;
sailfish_slot_manager_has_started(m);
}
static void sailfish_slot_manager_start(struct sailfish_slot_manager *m)
{
const struct sailfish_slot_driver *d = m->driver;
if (d->manager_start) {
m->start_id = d->manager_start(m->impl);
if (!m->start_id) {
sailfish_slot_manager_has_started(m);
}
}
}
static struct sailfish_slot_manager *sailfish_slot_manager_new
(struct sailfish_slot_driver_reg *r)
{
const struct sailfish_slot_driver *d = r->driver;
if (d->manager_create) {
struct sailfish_slot_manager *m =
g_slice_new0(struct sailfish_slot_manager);
m->driver = d;
m->plugin = r->plugin;
m->impl = d->manager_create(m);
if (m->impl) {
return m;
}
g_slice_free(struct sailfish_slot_manager, m);
}
return NULL;
}
static void sailfish_slot_manager_free(struct sailfish_slot_manager *m)
{
/* Ignore nested sailfish_slot_manager_free calls */
if (m && m->impl) {
const struct sailfish_slot_driver *driver = m->driver;
if (m->start_id && driver->manager_cancel_start) {
driver->manager_cancel_start(m->impl, m->start_id);
}
while (m->slots) {
struct sailfish_slot_priv *s = m->slots;
m->slots = s->next;
s->next = NULL;
sailfish_slot_free(s);
}
if (driver->manager_free) {
struct sailfish_slot_manager_impl *impl = m->impl;
m->impl = NULL;
driver->manager_free(impl);
}
g_slice_free(struct sailfish_slot_manager, m);
}
}
static int sailfish_slot_driver_compare(const struct sailfish_slot_driver *a,
const struct sailfish_slot_driver *b)
{
if (a->priority != b->priority) {
return a->priority - b->priority;
} else {
return -g_strcmp0(a->name, b->name);
}
}
static gboolean sailfish_slot_driver_init(gpointer user_data)
{
struct sailfish_slot_driver_reg *r = user_data;
r->init_id = 0;
r->manager = sailfish_slot_manager_new(r);
if (r->manager) {
sailfish_slot_manager_start(r->manager);
}
return G_SOURCE_REMOVE;
}
static struct sailfish_slot_driver_reg *sailfish_manager_priv_reg_new
(struct sailfish_manager_priv *p,
const struct sailfish_slot_driver *d)
{
struct sailfish_slot_driver_reg *r = NULL;
if (p) {
r = g_slice_new0(struct sailfish_slot_driver_reg);
r->plugin = p;
r->driver = d;
r->init_id = g_idle_add(sailfish_slot_driver_init, r);
if (!p->drivers || sailfish_slot_driver_compare
(p->drivers->driver, d) < 0) {
r->next = p->drivers;
p->drivers = r;
} else {
struct sailfish_slot_driver_reg *prev = p->drivers;
/* Keep the list sorted */
while (prev->next && sailfish_slot_driver_compare
(prev->next->driver, d) >= 0) {
prev = prev->next;
}
r->next = prev->next;
prev->next = r;
}
}
return r;
}
static void sailfish_slot_driver_free(struct sailfish_slot_driver_reg *r)
{
if (r->init_id) {
g_source_remove(r->init_id);
}
if (r->manager) {
sailfish_slot_manager_free(r->manager);
r->manager = NULL;
}
r->next = NULL;
g_slice_free(struct sailfish_slot_driver_reg, r);
}
static void sailfish_manager_priv_unreg(struct sailfish_manager_priv *p,
struct sailfish_slot_driver_reg *r)
{
if (r == p->drivers) {
p->drivers = r->next;
sailfish_slot_driver_free(r);
} else if (p->drivers) {
struct sailfish_slot_driver_reg *prev = p->drivers;
while (prev && prev->next != r) {
prev = prev->next;
}
if (prev) {
prev->next = r->next;
sailfish_slot_driver_free(r);
}
}
}
static gboolean sailfish_manager_priv_init(gpointer user_data)
{
struct sailfish_manager_priv *p = user_data;
p->init_countdown--;
if (!p->init_countdown) {
p->init_id = 0;
DBG("done with registrations");
if (!sailfish_manager_update_ready(p)) {
sailfish_manager_update_dbus_block(p);
}
return G_SOURCE_REMOVE;
} else {
/* Keep on waiting */
return G_SOURCE_CONTINUE;
}
}
static struct sailfish_manager_priv *sailfish_manager_priv_new()
{
static const struct sailfish_manager_dbus_cb dbus_cb = {
.get_errors = sailfish_manager_get_errors,
.get_slot_errors = sailfish_manager_get_slot_errors,
.set_enabled_slots = sailfish_manager_set_enabled_slots,
.set_mms_imsi = sailfish_manager_set_mms_imsi,
.set_default_voice_imsi =
sailfish_manager_set_default_voice_imsi,
.set_default_data_imsi =
sailfish_manager_set_default_data_imsi
};
struct sailfish_manager_priv *p =
g_slice_new0(struct sailfish_manager_priv);
GKeyFile *conf = g_key_file_new();
char* fn = g_build_filename(ofono_config_dir(), SF_CONFIG_FILE, NULL);
/* Load config */
if (g_key_file_load_from_file(conf, fn, 0, NULL)) {
int ival;
DBG("Loading configuration file %s", fn);
if (sailfish_config_get_enum(conf, SF_CONFIG_GROUP,
SF_CONFIG_KEY_AUTO_DATA_SIM, &ival,
"off", SIM_AUTO_SELECT_OFF,
"once", SIM_AUTO_SELECT_ONCE,
"always", SIM_AUTO_SELECT_ON,
"on", SIM_AUTO_SELECT_ON, NULL)) {
DBG("Automatic data SIM selection: %s",
ival == SIM_AUTO_SELECT_ONCE ? "once":
ival == SIM_AUTO_SELECT_ON ? "on":
"off");
p->auto_data_sim = ival;
}
}
g_key_file_free(conf);
g_free(fn);
/* Load settings */
p->storage = storage_open(NULL, SF_STORE);
p->pub.default_voice_imsi = p->default_voice_imsi =
g_key_file_get_string(p->storage, SF_STORE_GROUP,
SF_STORE_DEFAULT_VOICE_SIM, NULL);
p->pub.default_data_imsi = p->default_data_imsi =
g_key_file_get_string(p->storage, SF_STORE_GROUP,
SF_STORE_DEFAULT_DATA_SIM, NULL);
p->auto_data_sim_done = g_key_file_get_boolean(p->storage,
SF_STORE_GROUP, SF_STORE_AUTO_DATA_SIM_DONE, NULL);
DBG("Default voice sim is %s", p->default_voice_imsi ?
p->default_voice_imsi : "(auto)");
DBG("Default data sim is %s", p->default_data_imsi ?
p->default_data_imsi : "(auto)");
/* Delay the initialization until after all drivers get registered */
p->init_countdown = SF_INIT_IDLE_COUNT;
p->init_id = g_idle_add(sailfish_manager_priv_init, p);
/* And block all requests until that happens */
p->dbus = sailfish_manager_dbus_new(&p->pub, &dbus_cb);
sailfish_manager_dbus_set_block(p->dbus,
SAILFISH_MANAGER_DBUS_BLOCK_ALL);
return p;
}
static void sailfish_manager_priv_free(struct sailfish_manager_priv *p)
{
if (p) {
while (p->drivers) {
sailfish_manager_priv_unreg(p, p->drivers);
}
if (p->init_id) {
g_source_remove(p->init_id);
}
if (p->errors) {
g_hash_table_destroy(p->errors);
}
sailfish_manager_dbus_free(p->dbus);
g_key_file_free(p->storage);
g_free(p->default_voice_imsi);
g_free(p->default_data_imsi);
g_free(p->mms_imsi);
g_free(p->slots);
g_slice_free(struct sailfish_manager_priv, p);
}
}
void sailfish_manager_foreach_slot_manager
(struct sailfish_slot_driver_reg *r,
sailfish_slot_manager_impl_cb_t cb, void *user_data)
{
if (r && r->manager && cb) {
/* Yes, it's just one to one mapping but let's keep the API
* generic and allow many slot_manager instances. */
cb(r->manager->impl, user_data);
}
}
/* Global part (that requires access to sfos_manager_plugin variable) */
static struct sailfish_manager_priv *sfos_manager_plugin;
struct sailfish_slot_driver_reg *sailfish_slot_driver_register
(const struct sailfish_slot_driver *d)
{
if (d) {
DBG("%s", d->name);
/* This function can be invoked before sailfish_manager_init */
if (!sfos_manager_plugin) {
sfos_manager_plugin = sailfish_manager_priv_new();
}
/* Only allow registrations at startup */
if (sfos_manager_plugin->init_countdown) {
return sailfish_manager_priv_reg_new
(sfos_manager_plugin, d);
} else {
ofono_error("Refusing to register driver %s", d->name);
}
}
return NULL;
}
void sailfish_slot_driver_unregister(struct sailfish_slot_driver_reg *r)
{
if (r) {
DBG("%s", r->driver->name);
sailfish_manager_priv_unreg(sfos_manager_plugin, r);
}
}
static int sailfish_manager_init(void)
{
DBG("");
if (!sfos_manager_plugin) {
sfos_manager_plugin = sailfish_manager_priv_new();
}
return 0;
}
static void sailfish_manager_exit(void)
{
DBG("");
if (sfos_manager_plugin) {
sailfish_manager_priv_free(sfos_manager_plugin);
sfos_manager_plugin = NULL;
}
}
OFONO_PLUGIN_DEFINE(sailfish_manager, "Sailfish OS modem manager plugin",
VERSION, OFONO_PLUGIN_PRIORITY_DEFAULT,
sailfish_manager_init, sailfish_manager_exit)
/*
* Local Variables:
* mode: C
* c-basic-offset: 8
* indent-tabs-mode: t
* End:
*/