Files
ofono/ofono/drivers/ril/ril_connman.c
Slava Monich 71ef390b4a [ril] Improved device state management. JB#46836
Use RIL_REQUEST_SEND_DEVICE_STATE on newer devices, take charger
and battery state into account.
2019-08-28 19:07:17 +03:00

623 lines
16 KiB
C

/*
* oFono - Open Source Telephony - RIL-based devices
*
* Copyright (C) 2019 Jolla Ltd.
*
* 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 "ril_connman.h"
#include <ofono/log.h>
#include <gdbus.h>
#include <gutil_macros.h>
#include <gutil_misc.h>
#include <glib-object.h>
#define CONNMAN_BUS DBUS_BUS_SYSTEM
#define CONNMAN_SERVICE "net.connman"
#define CONNMAN_PATH "/"
#define CONNMAN_GET_PROPERTIES "GetProperties"
#define CONNMAN_GET_TECHNOLOGIES "GetTechnologies"
#define CONNMAN_PROPERTY_CHANGED "PropertyChanged"
#define CONNMAN_TECH_CONNECTED "Connected"
#define CONNMAN_TECH_TETHERING "Tethering"
#define CONNMAN_INTERFACE_(name) "net.connman." name
#define CONNMAN_MANAGER_INTERFACE CONNMAN_INTERFACE_("Manager")
#define CONNMAN_TECH_INTERFACE CONNMAN_INTERFACE_("Technology")
#define CONNMAN_TECH_PATH_(name) "/net/connman/technology/" name
#define CONNMAN_TECH_PATH_WIFI CONNMAN_TECH_PATH_("wifi")
#define CONNMAN_TECH_CONNECTED_BIT (0x01)
#define CONNMAN_TECH_TETHERING_BIT (0x02)
#define CONNMAN_TECH_ALL_PROPERTY_BITS (\
CONNMAN_TECH_CONNECTED_BIT | \
CONNMAN_TECH_TETHERING_BIT)
typedef GObjectClass ConnManObjectClass;
typedef struct connman_tech ConnManTech;
typedef struct connman_object {
GObject object;
struct ril_connman pub;
guint32 pending_signals;
DBusConnection *connection;
DBusPendingCall *call;
guint service_watch;
guint signal_watch;
GHashTable *techs;
ConnManTech *wifi;
} ConnManObject;
G_DEFINE_TYPE(ConnManObject, connman_object, G_TYPE_OBJECT)
#define CONNMAN_OBJECT_TYPE (connman_object_get_type())
#define CONNMAN_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\
CONNMAN_OBJECT_TYPE, ConnManObject))
struct connman_tech {
ConnManObject *obj;
const char *path;
gboolean connected;
gboolean tethering;
};
typedef struct connman_closure {
GCClosure cclosure;
ril_connman_property_cb_t callback;
gpointer user_data;
} ConnManClosure;
#define connman_closure_new() ((ConnManClosure *) \
g_closure_new_simple(sizeof(ConnManClosure), NULL))
#define SIGNAL_PROPERTY_CHANGED_NAME "ril-connman-property-changed"
#define SIGNAL_PROPERTY_DETAIL "%x"
#define SIGNAL_PROPERTY_DETAIL_MAX_LEN (8)
#define SIGNAL_BIT(property) (1 << (property - 1))
#define SIGNAL_BIT_(name) SIGNAL_BIT(RIL_CONNMAN_PROPERTY_##name)
enum connman_object_signal {
SIGNAL_PROPERTY_CHANGED,
SIGNAL_COUNT
};
static guint connman_object_signals[SIGNAL_COUNT];
static GQuark connman_object_property_quarks[RIL_CONNMAN_PROPERTY_COUNT - 1];
static inline ConnManObject *connman_object_cast(struct ril_connman *connman)
{
return G_LIKELY(connman) ?
CONNMAN_OBJECT(G_CAST(connman, ConnManObject, pub)) :
NULL;
}
static inline const char *connman_iter_get_string(DBusMessageIter *it)
{
const char *str = NULL;
dbus_message_iter_get_basic(it, &str);
return str;
}
static GQuark connman_object_property_quark(enum ril_connman_property p)
{
/* For ANY property this function is expected to return zero */
if (p > RIL_CONNMAN_PROPERTY_ANY && p < RIL_CONNMAN_PROPERTY_COUNT) {
const int i = p - 1;
if (G_UNLIKELY(!connman_object_property_quarks[i])) {
char buf[SIGNAL_PROPERTY_DETAIL_MAX_LEN + 1];
snprintf(buf, sizeof(buf), SIGNAL_PROPERTY_DETAIL, p);
buf[sizeof(buf) - 1] = 0;
connman_object_property_quarks[i] =
g_quark_from_string(buf);
}
return connman_object_property_quarks[i];
}
return 0;
}
static void connman_object_property_changed(ConnManObject *self,
enum ril_connman_property property, ConnManClosure *closure)
{
closure->callback(&self->pub, property, closure->user_data);
}
static void connman_object_emit_property_change(ConnManObject *self,
enum ril_connman_property p)
{
self->pending_signals &= ~SIGNAL_BIT(p);
g_signal_emit(self, connman_object_signals[SIGNAL_PROPERTY_CHANGED],
connman_object_property_quark(p), p);
}
static void connman_object_emit_pending_signals(ConnManObject *self)
{
struct ril_connman *connman = &self->pub;
gboolean valid_changed, present_changed;
enum ril_connman_property p;
/* Handlers could drop their references to us */
g_object_ref(self);
/*
* PRESENT and VALID are the last signals to be emitted if the object
* BECOMES present and/or valid.
*/
if ((self->pending_signals & SIGNAL_BIT_(VALID)) &&
connman->valid) {
self->pending_signals &= ~SIGNAL_BIT_(VALID);
valid_changed = TRUE;
} else {
valid_changed = FALSE;
}
if ((self->pending_signals & SIGNAL_BIT_(PRESENT)) &&
connman->present) {
self->pending_signals &= ~SIGNAL_BIT_(PRESENT);
present_changed = TRUE;
} else {
present_changed = FALSE;
}
/*
* Emit the signals. Not that in case if valid has become FALSE,
* then VALID is emitted first, otherwise it's emitted last.
* Same thing with PRESENT.
*/
for (p = RIL_CONNMAN_PROPERTY_ANY + 1;
p < RIL_CONNMAN_PROPERTY_COUNT && self->pending_signals;
p++) {
if (self->pending_signals & SIGNAL_BIT(p)) {
connman_object_emit_property_change(self, p);
}
}
/* Then emit PRESENT and VALID if necessary */
if (present_changed) {
connman_object_emit_property_change(self,
RIL_CONNMAN_PROPERTY_PRESENT);
}
if (valid_changed) {
connman_object_emit_property_change(self,
RIL_CONNMAN_PROPERTY_VALID);
}
/* And release the temporary reference */
g_object_unref(self);
}
static void connman_cancel_call(ConnManObject *self)
{
if (self->call) {
dbus_pending_call_cancel(self->call);
dbus_pending_call_unref(self->call);
self->call = NULL;
}
}
static ConnManTech *connman_tech_new(ConnManObject *self, const char *path)
{
ConnManTech *tech = g_new0(ConnManTech, 1);
char *key = g_strdup(path);
tech->obj = self;
tech->path = key;
g_hash_table_replace(self->techs, key, tech);
return tech;
}
static void connman_invalidate(ConnManObject *self)
{
struct ril_connman *connman = &self->pub;
if (connman->valid) {
connman->valid = FALSE;
self->pending_signals |= SIGNAL_BIT_(VALID);
}
}
static void connman_update_valid(ConnManObject *self)
{
struct ril_connman *connman = &self->pub;
const gboolean valid = (connman->present && !self->call);
if (connman->valid != valid) {
connman->valid = valid;
self->pending_signals |= SIGNAL_BIT_(VALID);
}
}
static gboolean connman_update_tethering(ConnManObject *self)
{
struct ril_connman *connman = &self->pub;
gboolean tethering = FALSE;
GHashTableIter it;
gpointer value;
g_hash_table_iter_init(&it, self->techs);
while (g_hash_table_iter_next(&it, NULL, &value)) {
const ConnManTech *tech = value;
if (tech->tethering) {
tethering = TRUE;
break;
}
}
if (connman->tethering != tethering) {
connman->tethering = tethering;
self->pending_signals |= SIGNAL_BIT_(TETHERING);
return TRUE;
} else {
return FALSE;
}
}
static void connman_set_tech_tethering(ConnManTech *tech, gboolean tethering)
{
if (tech->tethering != tethering) {
ConnManObject *self = tech->obj;
tech->tethering = tethering;
DBG(CONNMAN_TECH_TETHERING " %s for %s",
tethering ? "on" : "off", tech->path);
if (tethering) {
struct ril_connman *connman = &self->pub;
if (G_LIKELY(!connman->tethering)) {
/* Definitely tethering now */
connman->tethering = TRUE;
self->pending_signals |= SIGNAL_BIT_(TETHERING);
DBG("Tethering on");
}
} else if (connman_update_tethering(self)) {
/* Not tethering anymore */
DBG("Tethering off");
}
}
}
static void connman_set_tech_connected(ConnManTech *tech, gboolean connected)
{
if (tech->connected != connected) {
ConnManObject *self = tech->obj;
tech->connected = connected;
DBG(CONNMAN_TECH_CONNECTED " %s for %s",
connected ? "on" : "off", tech->path);
if (tech == self->wifi) {
struct ril_connman *connman = &self->pub;
connman->wifi_connected = connected;
self->pending_signals |= SIGNAL_BIT_(WIFI_CONNECTED);
DBG("WiFi %sconnected", connected ? "" : "dis");
}
}
}
static int connman_tech_set_property(ConnManTech *tech, DBusMessageIter *it)
{
DBusMessageIter var;
DBusBasicValue value;
const char *key = connman_iter_get_string(it);
dbus_message_iter_next(it);
dbus_message_iter_recurse(it, &var);
dbus_message_iter_get_basic(&var, &value);
if (!g_ascii_strcasecmp(key, CONNMAN_TECH_CONNECTED)) {
if (dbus_message_iter_get_arg_type(&var) == DBUS_TYPE_BOOLEAN) {
connman_set_tech_connected(tech, value.bool_val);
return CONNMAN_TECH_CONNECTED_BIT;
}
} else if (!g_ascii_strcasecmp(key, CONNMAN_TECH_TETHERING)) {
if (dbus_message_iter_get_arg_type(&var) == DBUS_TYPE_BOOLEAN) {
connman_set_tech_tethering(tech, value.bool_val);
return CONNMAN_TECH_TETHERING_BIT;
}
}
return 0;
}
static void connman_tech_set_properties(ConnManTech *tech, DBusMessageIter *it)
{
DBusMessageIter dict;
int handled = 0;
dbus_message_iter_recurse(it, &dict);
while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter entry;
dbus_message_iter_recurse(&dict, &entry);
handled |= connman_tech_set_property(tech, &entry);
if (handled == CONNMAN_TECH_ALL_PROPERTY_BITS) {
/* Ignore the rest */
break;
}
dbus_message_iter_next(&dict);
}
}
static gboolean connman_tech_property_changed(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
const char *path = dbus_message_get_path(msg);
ConnManObject *self = CONNMAN_OBJECT(user_data);
ConnManTech *tech = g_hash_table_lookup(self->techs, path);
DBusMessageIter it;
if (tech && dbus_message_has_signature(msg, "sv") &&
dbus_message_iter_init(msg, &it)) {
const char* name = connman_iter_get_string(&it);
if (!connman_tech_set_property(tech, &it)) {
DBG("%s changed for %s", name, path);
}
connman_object_emit_pending_signals(self);
}
return TRUE;
}
static void connman_set_techs(ConnManObject *self, DBusMessageIter *it)
{
DBusMessageIter list;
dbus_message_iter_recurse(it, &list);
while (dbus_message_iter_get_arg_type(&list) == DBUS_TYPE_STRUCT) {
DBusMessageIter entry;
const char *path;
ConnManTech *tech;
dbus_message_iter_recurse(&list, &entry);
path = connman_iter_get_string(&entry);
tech = connman_tech_new(self, path);
DBG("%s", path);
if (!g_strcmp0(path, CONNMAN_TECH_PATH_WIFI)) {
/* WiFi is a special case */
self->wifi = tech;
}
dbus_message_iter_next(&entry);
connman_tech_set_properties(tech, &entry);
dbus_message_iter_next(&list);
}
}
static void connman_techs_reply(DBusPendingCall *call, void *user_data)
{
ConnManObject *self = CONNMAN_OBJECT(user_data);
DBusMessage *reply = dbus_pending_call_steal_reply(call);
DBusError error;
DBusMessageIter array;
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, reply)) {
DBG("Failed to get technologies: %s", error.message);
dbus_error_free(&error);
} else if (dbus_message_has_signature(reply, "a(oa{sv})") &&
dbus_message_iter_init(reply, &array)) {
connman_set_techs(self, &array);
}
dbus_message_unref(reply);
dbus_pending_call_unref(self->call);
self->call = NULL;
connman_update_valid(self);
connman_object_emit_pending_signals(self);
}
static void connman_get_techs(ConnManObject *self)
{
DBusMessage *msg = dbus_message_new_method_call(CONNMAN_SERVICE,
CONNMAN_PATH, CONNMAN_MANAGER_INTERFACE,
CONNMAN_GET_TECHNOLOGIES);
connman_cancel_call(self);
if (g_dbus_send_message_with_reply(self->connection, msg,
&self->call, DBUS_TIMEOUT_INFINITE)) {
/* Not valid while any request is pending */
connman_invalidate(self);
dbus_pending_call_set_notify(self->call, connman_techs_reply,
self, NULL);
}
dbus_message_unref(msg);
}
static void connman_appeared(DBusConnection *conn, void *user_data)
{
ConnManObject *self = CONNMAN_OBJECT(user_data);
struct ril_connman *connman = &self->pub;
if (!connman->present) {
DBG("connman is there");
connman->present = TRUE;
self->pending_signals |= SIGNAL_BIT_(PRESENT);
connman_get_techs(self);
connman_object_emit_pending_signals(self);
}
}
static void connman_vanished(DBusConnection *conn, void *user_data)
{
ConnManObject *self = CONNMAN_OBJECT(user_data);
struct ril_connman *connman = &self->pub;
if (connman->present) {
DBG("connman has disappeared");
g_hash_table_remove_all(self->techs);
self->wifi = NULL;
connman->present = FALSE;
self->pending_signals |= SIGNAL_BIT_(PRESENT);
if (connman->wifi_connected) {
connman->wifi_connected = FALSE;
self->pending_signals |= SIGNAL_BIT_(WIFI_CONNECTED);
}
if (connman->tethering) {
connman->tethering = FALSE;
self->pending_signals |= SIGNAL_BIT_(TETHERING);
}
connman_object_emit_pending_signals(self);
}
}
static void connman_init(ConnManObject *self, DBusConnection *connection)
{
self->connection = dbus_connection_ref(connection);
self->service_watch = g_dbus_add_service_watch(self->connection,
CONNMAN_SERVICE, connman_appeared, connman_vanished,
self, NULL);
self->signal_watch = g_dbus_add_signal_watch(self->connection,
CONNMAN_SERVICE, NULL, CONNMAN_TECH_INTERFACE,
CONNMAN_PROPERTY_CHANGED, connman_tech_property_changed,
self, NULL);
}
struct ril_connman *ril_connman_new()
{
static ConnManObject *instance = NULL;
if (instance) {
g_object_ref(instance);
return &instance->pub;
} else {
DBusError error;
DBusConnection *connection;
dbus_error_init(&error);
connection = dbus_bus_get(CONNMAN_BUS, NULL);
if (connection) {
instance = g_object_new(CONNMAN_OBJECT_TYPE, NULL);
connman_init(instance, connection);
dbus_connection_unref(connection);
g_object_add_weak_pointer(G_OBJECT(instance),
(gpointer*)(&instance));
return &instance->pub;
} else {
ofono_error("Unable to attach to connman bus: %s",
error.message);
dbus_error_free(&error);
return NULL;
}
}
}
struct ril_connman *ril_connman_ref(struct ril_connman *connman)
{
ConnManObject *self = connman_object_cast(connman);
if (G_LIKELY(self)) {
g_object_ref(self);
return connman;
} else {
return NULL;
}
}
void ril_connman_unref(struct ril_connman *connman)
{
ConnManObject *self = connman_object_cast(connman);
if (G_LIKELY(self)) {
g_object_unref(self);
}
}
gulong ril_connman_add_property_changed_handler(struct ril_connman *connman,
enum ril_connman_property p, ril_connman_property_cb_t cb, void *arg)
{
ConnManObject *self = connman_object_cast(connman);
if (G_LIKELY(self) && G_LIKELY(cb)) {
/*
* We can't directly connect the provided callback because
* it expects the first parameter to point to public part
* of the object but glib will call it with ConnManObject
* as the first parameter. connman_object_property_changed()
* will do the conversion.
*/
ConnManClosure *closure = connman_closure_new();
GCClosure *cc = &closure->cclosure;
cc->closure.data = closure;
cc->callback = G_CALLBACK(connman_object_property_changed);
closure->callback = cb;
closure->user_data = arg;
return g_signal_connect_closure_by_id(self,
connman_object_signals[SIGNAL_PROPERTY_CHANGED],
connman_object_property_quark(p), &cc->closure, FALSE);
}
return 0;
}
void ril_connman_remove_handler(struct ril_connman *connman, gulong id)
{
if (G_LIKELY(id)) {
ConnManObject *self = connman_object_cast(connman);
if (G_LIKELY(self)) {
g_signal_handler_disconnect(self, id);
}
}
}
void ril_connman_remove_handlers(struct ril_connman *connman, gulong *ids,
int n)
{
gutil_disconnect_handlers(connman_object_cast(connman), ids, n);
}
static void connman_object_init(ConnManObject *self)
{
self->techs = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, g_free);
}
static void connman_object_finalize(GObject *object)
{
ConnManObject *self = CONNMAN_OBJECT(object);
connman_cancel_call(self);
g_hash_table_destroy(self->techs);
g_dbus_remove_watch(self->connection, self->service_watch);
g_dbus_remove_watch(self->connection, self->signal_watch);
dbus_connection_unref(self->connection);
G_OBJECT_CLASS(connman_object_parent_class)->finalize(object);
}
static void connman_object_class_init(ConnManObjectClass *klass)
{
G_OBJECT_CLASS(klass)->finalize = connman_object_finalize;
connman_object_signals[SIGNAL_PROPERTY_CHANGED] =
g_signal_new(SIGNAL_PROPERTY_CHANGED_NAME,
G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
}
/*
* Local Variables:
* mode: C
* c-basic-offset: 8
* indent-tabs-mode: t
* End:
*/