CHROMIUM: Add reconnect mechanism to HFP/HSP
A2DP has reconnect mechanism implemented in plugin/policy.c,
at connection failure with err code -EAGAIN, a timer will be
scheduled to initiate connection in 2 seconds.
A confusing scenario is observed occasionally that user got
only A2DP when a BT headset is connected, but not HFP or HSP.
When it happens, user is unable to use bidrectional audio, and
with headset disconnected and then reconnected, both A2DP and
HFP/HSP work fine.
This change adds similar reconnect mechanism for HFP/HSP, so
that Chrome OS users can have more consistent experience on
BT audio.
BUG=chromium:527575
TEST=Connect BT headset, disconnect and reconnect. Verify
this audio device cab be used for A2DP and HFP/HSP.
Change-Id: I97afbf7a6919309514bfa0e0446b54b6c85ab819
Reviewed-on: https://chromium-review.googlesource.com/304441
Commit-Ready: Hsinyu Chao <hychao@chromium.org>
Tested-by: Hsinyu Chao <hychao@chromium.org>
Reviewed-by: Miao-chen Chou <mcchou@chromium.org>
diff --git a/plugins/chromium.c b/plugins/chromium.c
index e886075..c9ac930 100644
--- a/plugins/chromium.c
+++ b/plugins/chromium.c
@@ -8,6 +8,7 @@
#include <config.h>
#endif
+#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
@@ -19,12 +20,16 @@
#include "lib/mgmt.h"
#include "lib/sdp.h"
+#include "lib/uuid.h"
+
#include "src/adapter.h"
#include "src/dbus-common.h"
#include "src/device.h"
#include "src/error.h"
#include "src/log.h"
#include "src/plugin.h"
+#include "src/profile.h"
+#include "src/service.h"
#include "src/shared/mgmt.h"
#define DBUS_PATH "/org/bluez"
@@ -36,6 +41,9 @@
#define DBUS_BLUEZ_DEVICE_INTERFACE "org.bluez.Device1"
+#define SERVICE_RETRIES 1
+#define SERVICE_RETRY_TIMEOUT 2
+
static struct mgmt *mgmt_if = NULL;
static bool supports_le_services = false;
@@ -44,6 +52,47 @@
static int interfaces_added_watch_id = 0;
static int interfaces_removed_watch_id = 0;
+static unsigned int service_id = 0;
+
+static const char *services_to_reconnect[] = {
+ HSP_AG_UUID, HFP_AG_UUID, NULL };
+static GSList *retry_devices = NULL;
+
+struct retry_data {
+ struct btd_device *dev;
+ uint8_t retries;
+ guint timer;
+};
+
+static void destroy_retry_data(void* user_data)
+{
+ struct retry_data *data = user_data;
+
+ if (data->timer > 0)
+ g_source_remove(data->timer);
+
+ g_free(data);
+}
+
+static struct retry_data *get_retry_data(struct btd_device *dev)
+{
+ struct retry_data *data;
+ GSList *l;
+
+ for (l = retry_devices; l ; l = l->next) {
+ struct retry_data *data = l->data;
+
+ if (data->dev == dev)
+ return data;
+ }
+
+ data = g_new0(struct retry_data, 1);
+ data->dev = dev;
+
+ retry_devices = g_slist_prepend(retry_devices, data);
+ return data;
+}
+
static gboolean chromium_property_get_supports_le_services(
const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
@@ -302,6 +351,120 @@
{ }
};
+/* Checks if any type of audio gateway is connected. There are two profiles
+ * we care about here: HFP and HSP. */
+static gboolean is_dev_connected(struct btd_device *dev)
+{
+ struct btd_service *service;
+ const char **uuid;
+
+ for (uuid = services_to_reconnect; *uuid; uuid++) {
+ service = btd_device_get_service(dev, *uuid);
+ if (service == NULL)
+ continue;
+ if (btd_service_get_state(service) ==
+ BTD_SERVICE_STATE_CONNECTED)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean connect_dev(gpointer user_data)
+{
+ struct retry_data *data = user_data;
+ struct btd_service *service;
+ struct btd_profile *profile;
+ const char **uuid;
+
+ data->timer = 0;
+ data->retries++;
+
+ if (is_dev_connected(data->dev))
+ return FALSE;
+
+ for (uuid = services_to_reconnect; *uuid; uuid++) {
+ service = btd_device_get_service(data->dev, *uuid);
+ if (service == NULL)
+ continue;
+
+ profile = btd_service_get_profile(service);
+ info("Reconnect profile %s", profile->name);
+
+ btd_service_connect(service);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void set_timer(gpointer user_data)
+{
+ struct retry_data *data = user_data;
+
+ if (is_dev_connected(data->dev))
+ return;
+
+ if (data->timer == 0)
+ data->timer = g_timeout_add_seconds(SERVICE_RETRY_TIMEOUT,
+ connect_dev, data);
+}
+
+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_device *dev = btd_service_get_device(service);
+ struct btd_profile *profile = btd_service_get_profile(service);
+ struct retry_data *data;
+ const char **uuid;
+ bool reconnect = false;
+
+ for (uuid = services_to_reconnect; *uuid; uuid++) {
+ if (g_str_equal(profile->remote_uuid, *uuid)) {
+ reconnect = true;
+ break;
+ }
+ }
+ if (!reconnect)
+ return;
+
+ data = get_retry_data(dev);
+
+ switch (new_state) {
+ case BTD_SERVICE_STATE_UNAVAILABLE:
+ if (data->timer > 0) {
+ g_source_remove(data->timer);
+ data->timer = 0;
+ }
+ break;
+ case BTD_SERVICE_STATE_DISCONNECTED:
+ if (old_state == BTD_SERVICE_STATE_CONNECTING) {
+ int err = btd_service_get_error(service);
+
+ if (err == -EAGAIN) {
+ if (data->retries < SERVICE_RETRIES)
+ set_timer(data);
+ else
+ data->retries = 0;
+ } else if (data->timer > 0) {
+ g_source_remove(data->timer);
+ data->timer = 0;
+ }
+ }
+ break;
+ case BTD_SERVICE_STATE_CONNECTING:
+ break;
+ case BTD_SERVICE_STATE_CONNECTED:
+ if (data->timer > 0) {
+ g_source_remove(data->timer);
+ data->timer = 0;
+ }
+ break;
+ case BTD_SERVICE_STATE_DISCONNECTING:
+ break;
+ }
+}
+
static void read_version_complete(uint8_t status, uint16_t length,
const void *param, void *user_data)
{
@@ -349,6 +512,8 @@
DBUS_PATH, DBUS_PLUGIN_INTERFACE,
NULL, NULL, chromium_properties, NULL, NULL);
+ service_id = btd_service_add_state_cb(service_cb, NULL);
+
/* Listen for new device objects being added so we can add the plugin
* interface to them.
*/
@@ -380,6 +545,9 @@
mgmt_unref(mgmt_if);
mgmt_if = NULL;
+ btd_service_remove_state_cb(service_id);
+ g_slist_free_full(retry_devices, destroy_retry_data);
+
remove_dbus_watches();
}