From ccac2886c516aa0ac2e76406b782de0abc700e92 Mon Sep 17 00:00:00 2001
From: Dylan Van Assche <me@dylanvanassche.be>
Date: Thu, 14 Apr 2022 19:47:46 +0200
Subject: [PATCH 11/26] bluetooth: support AT+COPS

AT+COPS=3,X sets the operator name format and AT+COPS? returns
the current network operator name. Supports this command when
ModemManager is available to provide this information.
---
 src/modules/bluetooth/backend-native.c | 72 +++++++++++++++++++++++++-
 src/modules/bluetooth/bluez5-util.h    | 11 +++-
 2 files changed, 81 insertions(+), 2 deletions(-)

diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c
index 89a174f9a..c0c14acf3 100644
--- a/src/modules/bluetooth/backend-native.c
+++ b/src/modules/bluetooth/backend-native.c
@@ -40,6 +40,7 @@
 #include "bluez5-util.h"
 #include "bt-codec-msbc.h"
 #include "upower.h"
+#include "modemmanager.h"
 
 #define MANDATORY_CALL_INDICATORS \
         "(\"call\",(0-1))," \
@@ -53,12 +54,14 @@ struct pa_bluetooth_backend {
   pa_hook_slot *adapter_uuids_changed_slot;
   pa_hook_slot *host_battery_level_changed_slot;
   pa_upower_backend *upower;
+  pa_modemmanager_backend *modemmanager;
   bool enable_shared_profiles;
   bool enable_hsp_hs;
   bool enable_hfp_hf;
   bool cmer_indicator_reporting_enabled;
   bool cmee_extended_error_reporting_enabled;
   uint32_t cind_enabled_indicators;
+  pa_bluetooth_cops_t cops_format;
 
   PA_LLIST_HEAD(pa_dbus_pending, pending);
 };
@@ -624,7 +627,7 @@ 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, mode, val;
+    int indicator, mode, val, val2;
     char str[5];
     const char *r;
     size_t len;
@@ -659,6 +662,68 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
         /* Noise Redction and Echo Canceling can only have value '0' (disable) following Bluetooth HFP 1.8 */
         rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_ALLOWED);
 	return false;
+    } else if (sscanf(buf, "AT+COPS=%d,%d", &val, &val2) == 2) {
+        /* Return error if ModemManager is unavailable */
+        if (!discovery->native_backend->modemmanager || !pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) {
+            pa_log_debug("ModemManager backend unavailable, cannot set AT+COPS format");
+            rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE);
+            return false;
+        }
+
+        /* Only AT+COPS=3,X is allowed by Bluetooth HFP 1.8 */
+        if (val != 3)
+            rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_ALLOWED);
+
+	/* Set AT+COPS format */
+	if (val2 == 0) {
+           discovery->native_backend->cops_format = COPS_LONG_FORMAT;
+           return true;
+        } else if (val2 == 2) {
+           discovery->native_backend->cops_format = COPS_NUMERIC_FORMAT;
+           return true;
+        } else {
+           discovery->native_backend->cops_format = COPS_UNKNOWN_FORMAT;
+           rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_ALLOWED);
+	   return false;
+	}
+    } else if (strstr(buf, "AT+COPS?")) {
+        /* Return error if ModemManager is unavailable */
+        if (!discovery->native_backend->modemmanager || !pa_modemmanager_has_modem(discovery->native_backend->modemmanager)) {
+            pa_log_debug("ModemManager backend unavailable, cannot answer AT+COPS?");
+            rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_CONNECTION_TO_PHONE);
+            return false;
+        } else if (!pa_modemmanager_has_service(discovery->native_backend->modemmanager)) {
+            pa_log_debug("No network service, cannot answer AT+COPS?");
+            rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_NETWORK_SERVICE);
+            return false;
+        }
+
+        switch(discovery->native_backend->cops_format) {
+            case COPS_LONG_FORMAT: {
+                char *operator_name = pa_modemmanager_get_operator_name(discovery->native_backend->modemmanager);
+                if (operator_name) {
+                    rfcomm_write_response(fd, "+COPS: 0,0,\"%s\"", operator_name);
+                    return true;
+                }
+                rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_NETWORK_SERVICE);
+                return false;
+            }
+	    case COPS_NUMERIC_FORMAT: {
+                char *operator_code = pa_modemmanager_get_operator_code(discovery->native_backend->modemmanager);
+                if (operator_code) {
+                    rfcomm_write_response(fd, "+COPS: 2,0,\"%s\"", operator_code);
+                    return true;
+                }
+                rfcomm_write_error(discovery->native_backend, fd, CMEE_NO_NETWORK_SERVICE);
+                return false;
+            }
+            case COPS_UNKNOWN_FORMAT: {
+                rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE);
+                return false;
+            }
+            default:
+                pa_assert_not_reached();
+        }
     }
 
     /* first-time initialize selected codec to CVSD */
@@ -1441,6 +1506,8 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d
 
     backend->upower = pa_upower_backend_new(c, y);
 
+    backend->modemmanager = pa_modemmanager_backend_new(c, y);
+
     /* 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);
@@ -1473,6 +1540,9 @@ void pa_bluetooth_native_backend_free(pa_bluetooth_backend *backend) {
 
     if (backend->upower)
         pa_upower_backend_free(backend->upower);
+    
+    if (backend->modemmanager)
+        pa_modemmanager_backend_free(backend->modemmanager);
 
     pa_dbus_connection_unref(backend->connection);
 
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index fa26a8361..9ac9880a8 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -214,10 +214,19 @@ struct pa_bluetooth_adapter {
 /* extended error reporting values, described in Bluetooth HFP 1.8 spec */
 typedef enum pa_bluetooth_cmee {
     CMEE_AG_FAILURE = 0,
+    CMEE_NO_CONNECTION_TO_PHONE = 1,
     CMEE_OPERATION_NOT_ALLOWED = 3,
-    CMEE_OPERATION_NOT_SUPPORTED = 4
+    CMEE_OPERATION_NOT_SUPPORTED = 4,
+    CMEE_NO_NETWORK_SERVICE = 30
 } pa_bluetooth_cmee_t;
 
+/* AT+COPS format, described in Bluetooth HFP 1.8 spec */
+typedef enum pa_bluetooth_cops {
+    COPS_LONG_FORMAT = 0,
+    COPS_NUMERIC_FORMAT = 1,
+    COPS_UNKNOWN_FORMAT = 2
+} pa_bluetooth_cops_t;
+
 #ifdef HAVE_BLUEZ_5_OFONO_HEADSET
 pa_bluetooth_backend *pa_bluetooth_ofono_backend_new(pa_core *c, pa_bluetooth_discovery *y);
 void pa_bluetooth_ofono_backend_free(pa_bluetooth_backend *b);
-- 
2.35.1