From 5aebba47f8215d5c580602a34082fc27081d963d Mon Sep 17 00:00:00 2001
From: Dylan Van Assche <me@dylanvanassche.be>
Date: Tue, 5 Apr 2022 20:10:05 +0200
Subject: [PATCH 02/26] bluetooth: add AT+BIA support

AT+BIA is used to enable/disable CIND indicators by Bluetooth HFP spec.
By default, all indicators are enabled on connection.
AT+BIA will configure which indicators should be disabled then,
the disabled indicators may be enabled later on again with AT+BIA.
When the connection is lost and recovered, all indicators are enabled
again. The HF will reconfigure the indicators again with an AT+BIA
command.
---
 src/modules/bluetooth/backend-native.c | 93 ++++++++++++++++++++++++--
 1 file changed, 87 insertions(+), 6 deletions(-)

diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c
index f6b85e90d..d26edc1c9 100644
--- a/src/modules/bluetooth/backend-native.c
+++ b/src/modules/bluetooth/backend-native.c
@@ -39,6 +39,11 @@
 #include "bluez5-util.h"
 #include "bt-codec-msbc.h"
 
+#define MANDATORY_CALL_INDICATORS \
+        "(\"call\",(0-1))," \
+        "(\"callsetup\",(0-3))," \
+        "(\"callheld\",(0-2))" \
+
 struct pa_bluetooth_backend {
   pa_core *core;
   pa_dbus_connection *connection;
@@ -47,6 +52,8 @@ struct pa_bluetooth_backend {
   bool enable_shared_profiles;
   bool enable_hsp_hs;
   bool enable_hfp_hf;
+  bool cmer_indicator_reporting_enabled;
+  uint32_t cind_enabled_indicators;
 
   PA_LLIST_HEAD(pa_dbus_pending, pending);
 };
@@ -97,6 +104,20 @@ enum hfp_ag_features {
     HFP_AG_INDICATORS = 10,
 };
 
+/*
+ * Always keep this struct in sync with indicator discovery of AT+CIND=?
+ * These indicators are used in bitflags and intentionally start at 1
+ * since AT+CIND indicators start at index 1.
+ */
+typedef enum pa_bluetooth_ag_to_hf_indicators {
+    CIND_CALL_INDICATOR = 1,
+    CIND_CALL_SETUP_INDICATOR = 2,
+    CIND_CALL_HELD_INDICATOR = 3,
+    CIND_SERVICE_INDICATOR = 4,
+    CIND_BATT_CHG_INDICATOR = 5,
+    CIND_INDICATOR_MAX = 6
+} pa_bluetooth_ag_to_hf_indicators_t;
+
 /* gateway features we support, which is as little as we can get away with */
 static uint32_t hfp_features =
     /* HFP 1.6 requires this */
@@ -588,8 +609,9 @@ static pa_volume_t set_source_volume(pa_bluetooth_transport *t, pa_volume_t volu
 
 static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf)
 {
+    struct pa_bluetooth_discovery *discovery = t->device->discovery;
     struct hfp_config *c = t->config;
-    int indicator, val;
+    int indicator, mode, val;
     char str[5];
     const char *r;
     size_t len;
@@ -607,6 +629,34 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
         c->supports_indicators = !!(1 << HFP_HF_INDICATORS);
         c->state = 1;
 
+        return true;
+    } else if (sscanf(buf, "AT+BIA=%s", str) == 1) {
+        /* Indicators start with index 1 and follow the order of the AT+CIND=? response */
+        indicator = 1;
+
+        while ((r = pa_split_in_place(str, ",", &len, &state))) {
+            /* Ignore updates to mandantory indicators which are always ON */
+            if (indicator == CIND_CALL_INDICATOR 
+                || indicator == CIND_CALL_SETUP_INDICATOR
+                || indicator == CIND_CALL_HELD_INDICATOR) 
+                continue;
+
+            /* Indicators may have no value and should be skipped */
+            if (len == 0)
+                continue;
+
+            if (len == 1 && r[0] == '1')
+                discovery->native_backend->cind_enabled_indicators |= (1 << indicator);
+            else if (len == 1 && r[0] == '0')
+                discovery->native_backend->cind_enabled_indicators &= ~(1 << indicator);
+	    else {
+                pa_log_error("Unable to parse indicator of AT+BIA command: %s", buf);
+                rfcomm_write_response(fd, "ERROR");
+	        return false;
+            }
+
+            indicator++;
+        }
         return true;
     } else if (sscanf(buf, "AT+BAC=%3s", str) == 1) {
         c->support_msbc = false;
@@ -637,10 +687,8 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
         rfcomm_write_response(fd, "+CIND: "
                      /* many indicators can be supported, only call and
                       * callheld are mandatory, so that's all we reply */
-                     "(\"service\",(0-1)),"
-                     "(\"call\",(0-1)),"
-                     "(\"callsetup\",(0-3)),"
-                     "(\"callheld\",(0-2))");
+                     MANDATORY_CALL_INDICATORS ",",
+                     "(\"service\",(0-1))");
         c->state = 2;
 
         return true;
@@ -650,7 +698,24 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
 
         return true;
     } else if ((c->state == 2 || c->state == 3) && pa_startswith(buf, "AT+CMER=")) {
-        rfcomm_write_response(fd, "OK");
+	if (sscanf(buf, "AT+CMER=%d,%*d,%*d,%d", &mode, &val) == 2) {
+            /* Bluetooth HFP spec only defines mode == 3 */
+            if (mode != 3) {
+                pa_log_warn("Unexpected mode for AT+CMER: %d", mode);
+	    }
+
+	    /* Configure CMER event reporting */
+	    discovery->native_backend->cmer_indicator_reporting_enabled = !!val;
+
+            pa_log_debug("Event indications enabled? %s", pa_yes_no(val));
+
+            rfcomm_write_response(fd, "OK");
+        }
+        else {
+            pa_log_error("Unable to parse AT+CMER command: %s", buf);
+            rfcomm_write_response(fd, "ERROR");
+	    return false;
+        }
 
         if (c->support_codec_negotiation) {
             if (c->support_msbc && pa_bluetooth_discovery_get_enable_msbc(t->device->discovery)) {
@@ -740,6 +805,8 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
 
 static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
     pa_bluetooth_transport *t = userdata;
+    pa_bluetooth_discovery *discovery = t->device->discovery;
+    int i;
 
     pa_assert(io);
     pa_assert(t);
@@ -860,6 +927,11 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
     return;
 
 fail:
+    /* Service Connection lost, reset indicators and event reporting to default values */
+    discovery->native_backend->cmer_indicator_reporting_enabled = false;
+    for (i = 1; i < CIND_INDICATOR_MAX; i++)
+        discovery->native_backend->cind_enabled_indicators |= (1 << i);
+
     pa_bluetooth_transport_unlink(t);
     pa_bluetooth_transport_free(t);
 }
@@ -1188,6 +1260,7 @@ void pa_bluetooth_native_backend_enable_shared_profiles(pa_bluetooth_backend *na
 pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_shared_profiles) {
     pa_bluetooth_backend *backend;
     DBusError err;
+    int i;
 
     pa_log_debug("Bluetooth Headset Backend API support using the native backend");
 
@@ -1220,6 +1293,14 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d
     if (backend->enable_shared_profiles)
         native_backend_apply_profile_registration_change(backend, true);
 
+    /* All CIND indicators are enabled by default until overriden by AT+BIA */
+    for (i = 1; i < CIND_INDICATOR_MAX; i++)
+        backend->cind_enabled_indicators |= (1 << i);
+
+    /* While all CIND indicators are enabled, event reporting is not enabled by default */
+    backend->cmer_indicator_reporting_enabled = false;
+
+
     return backend;
 }
 
-- 
2.35.1

