summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@intel.com>2014-05-13 12:30:50 +0300
committerJohan Hedberg <johan.hedberg@intel.com>2014-05-15 11:16:33 +0300
commite0929a6e88e5f86d42c71ab8446c97a74b0e8172 (patch)
treef123226ae80d245faf025abf320a3629ea5d8322 /plugins
parentb98bb6c1027b2a6af85465b418f0ee893946a0fd (diff)
downloadbluez-e0929a6e88e5f86d42c71ab8446c97a74b0e8172.tar.gz
plugins/policy: Add basic reconnection handling
Diffstat (limited to 'plugins')
-rw-r--r--plugins/policy.c190
1 files changed, 190 insertions, 0 deletions
diff --git a/plugins/policy.c b/plugins/policy.c
index 0292482c3..ec2a3fa01 100644
--- a/plugins/policy.c
+++ b/plugins/policy.c
@@ -32,6 +32,7 @@
#include <glib.h>
#include "lib/uuid.h"
+#include "lib/mgmt.h"
#include "src/log.h"
#include "src/plugin.h"
#include "src/adapter.h"
@@ -45,6 +46,22 @@
#define SOURCE_RETRIES 1
#define SINK_RETRIES SOURCE_RETRIES
+/* Tracking of remote services to be auto-reconnected upon link loss */
+
+#define RECONNECT_TIMEOUT 1
+
+struct reconnect_data {
+ struct btd_device *dev;
+ bool reconnect;
+ GSList *services;
+ guint timer;
+};
+
+static const char *default_reconnect[] = {
+ HSP_AG_UUID, HFP_AG_UUID, A2DP_SOURCE_UUID, NULL };
+static char **reconnect = NULL;
+static GSList *reconnects = NULL;
+
static unsigned int service_id = 0;
static GSList *devices = NULL;
@@ -402,12 +419,100 @@ static void target_cb(struct btd_service *service,
}
}
+static bool reconnect_match(const char *uuid)
+{
+ char **str;
+
+ for (str = reconnect; *str; str++) {
+ if (!bt_uuid_strcmp(uuid, *str))
+ return true;
+ }
+
+ return false;
+}
+
+static struct reconnect_data *reconnect_find(struct btd_device *dev)
+{
+ GSList *l;
+
+ for (l = reconnects; l; l = g_slist_next(l)) {
+ struct reconnect_data *reconnect = l->data;
+
+ if (reconnect->dev == dev)
+ return reconnect;
+ }
+
+ return NULL;
+}
+
+static struct reconnect_data *reconnect_add(struct btd_service *service)
+{
+ struct btd_device *dev = btd_service_get_device(service);
+ struct reconnect_data *reconnect;
+
+ reconnect = reconnect_find(dev);
+ if (!reconnect) {
+ reconnect = g_new0(struct reconnect_data, 1);
+ reconnect->dev = dev;
+ reconnects = g_slist_append(reconnects, reconnect);
+ }
+
+ if (g_slist_find(reconnect->services, service))
+ return reconnect;
+
+ reconnect->services = g_slist_append(reconnect->services,
+ btd_service_ref(service));
+
+ return reconnect;
+}
+
+static void reconnect_destroy(gpointer data)
+{
+ struct reconnect_data *reconnect = data;
+
+ if (reconnect->timer > 0)
+ g_source_remove(reconnect->timer);
+
+ g_slist_free_full(reconnect->services,
+ (GDestroyNotify) btd_service_unref);
+ g_free(reconnect);
+}
+
+static void reconnect_remove(struct btd_service *service)
+{
+ struct btd_device *dev = btd_service_get_device(service);
+ struct reconnect_data *reconnect;
+ GSList *l;
+
+ reconnect = reconnect_find(dev);
+ if (!reconnect)
+ return;
+
+ l = g_slist_find(reconnect->services, service);
+ if (!l)
+ return;
+
+ reconnect->services = g_slist_delete_link(reconnect->services, l);
+ btd_service_unref(service);
+
+ if (reconnect->services)
+ return;
+
+ reconnects = g_slist_remove(reconnects, reconnect);
+
+ if (reconnect->timer > 0)
+ g_source_remove(reconnect->timer);
+
+ g_free(reconnect);
+}
+
static void service_cb(struct btd_service *service,
btd_service_state_t old_state,
btd_service_state_t new_state,
void *user_data)
{
struct btd_profile *profile = btd_service_get_profile(service);
+ struct reconnect_data *reconnect;
if (g_str_equal(profile->remote_uuid, A2DP_SINK_UUID))
sink_cb(service, old_state, new_state);
@@ -417,17 +522,102 @@ static void service_cb(struct btd_service *service,
controller_cb(service, old_state, new_state);
else if (g_str_equal(profile->remote_uuid, AVRCP_TARGET_UUID))
target_cb(service, old_state, new_state);
+
+ /*
+ * We're only interested in reconnecting profiles which have set
+ * auto_connect to true.
+ */
+ if (!profile->auto_connect)
+ return;
+
+ /*
+ * If the service went away remove it from the reconnection
+ * tracking. The function will remove the entire tracking data
+ * if this was the last service for the device.
+ */
+ if (new_state == BTD_SERVICE_STATE_UNAVAILABLE) {
+ reconnect_remove(service);
+ return;
+ }
+
+ if (new_state != BTD_SERVICE_STATE_CONNECTED)
+ return;
+
+ /*
+ * Add an entry to track reconnections. The function will return
+ * an existing entry if there is one.
+ */
+ reconnect = reconnect_add(service);
+
+ /*
+ * Should this device be reconnected? A matching UUID might not
+ * be the first profile that's connected so we might have an
+ * entry but with the reconnect flag set to false.
+ */
+ if (!reconnect->reconnect)
+ reconnect->reconnect = reconnect_match(profile->remote_uuid);
+
+ DBG("Added %s reconnect %u", profile->name, reconnect->reconnect);
+}
+
+static gboolean reconnect_timeout(gpointer data)
+{
+ struct reconnect_data *reconnect = data;
+ int err;
+
+ DBG("Reconnecting profiles");
+
+ reconnect->timer = 0;
+
+ err = btd_device_connect_services(reconnect->dev, reconnect->services);
+ if (err < 0)
+ error("Reconnecting services failed: %s (%d)",
+ strerror(-err), -err);
+
+ return FALSE;
+}
+
+static void disconnect_cb(struct btd_device *dev, uint8_t reason)
+{
+ struct reconnect_data *reconnect;
+
+ DBG("reason %u", reason);
+
+ if (reason != MGMT_DEV_DISCONN_TIMEOUT)
+ return;
+
+ reconnect = reconnect_find(dev);
+ if (!reconnect || !reconnect->reconnect)
+ return;
+
+ DBG("Device %s identified for auto-reconnection",
+ device_get_path(dev));
+
+ reconnect->timer = g_timeout_add_seconds(RECONNECT_TIMEOUT,
+ reconnect_timeout,
+ reconnect);
}
static int policy_init(void)
{
service_id = btd_service_add_state_cb(service_cb, NULL);
+ /* TODO: Add overriding default from config file */
+ reconnect = g_strdupv((char **) default_reconnect);
+
+ btd_add_disconnect_cb(disconnect_cb);
+
return 0;
}
static void policy_exit(void)
{
+ btd_remove_disconnect_cb(disconnect_cb);
+
+ g_strfreev(reconnect);
+
+ g_slist_free_full(reconnects, reconnect_destroy);
+
g_slist_free_full(devices, policy_remove);
btd_service_remove_state_cb(service_id);