Files
2026-08-24 10:03:24 +08:00

712 lines
16 KiB
C

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pthread.h>
#include "lib/bluetooth.h"
#include "lib/sdp.h"
#include "gdbus/gdbus.h"
#include "src/dbus-common.h"
#include "src/plugin.h"
#include "src/adapter.h"
#include "src/log.h"
/********************************************************************/
/*Dev strategy: */
/*0: not strategy, no limit to connected device number */
/* */
/*1: only one connected device allowed, */
/* always reject next device till prev device disconnected */
/* */
/*2: only one connected device allowed, */
/* disconnect prev device when next device request connection */
/*3: Smart Strategy */
/* Use strategy 1 in conditions: */
/* a. in the first 10s after device connected */
/* b. while conncted device is playing music */
/* c. while conncted device is making phonecall */
/* */
/* In other condisions, use strategy 2 */
/* */
/********************************************************************/
#define DEV_STRATEGY 2
#define RESERVED_DEVICE_FILE "/etc/bluetooth/reserved_device"
static GDBusClient *client = NULL;
static GDBusProxy *adapter_proxy = NULL;
static GDBusProxy *reserved_device_proxy = NULL;
static const char *reserved_device_path = NULL;
static GList *dev_list = NULL;
static char SRT[2] = {5, 2}; //Startup reconnect timeout
static gboolean connect_dev(void* user_data);
static void connect_dev_return(DBusMessage *message, void *user_data);
static int set_discoverable(int enable);
static int set_pairable(int enable);
static int disconnect_prev_dev();
static int disconnect_next_dev();
#if (DEV_STRATEGY == 3)
static int hfp_ctl_sk;
static pthread_t phfp_ctl_id;
#define HFP_CTL_SK_PATH "/etc/bluetooth/hfp_ctl_sk"
#define HFP_EVENT_CONNECTION 1
#define HFP_EVENT_CALL 2
#define HFP_EVENT_CALLSETUP 3
#define HFP_EVENT_VGS 4
#define HFP_EVENT_VGM 5
#define HFP_IND_DEVICE_DISCONNECTED 0
#define HFP_IND_DEVICE_CONNECTED 1
#define HFP_IND_CALL_NONE 0
/* at least one call is in progress */
#define HFP_IND_CALL_ACTIVE 1
/* currently not in call set up */
#define HFP_IND_CALLSETUP_NONE 0
/* an incoming call process ongoing */
#define HFP_IND_CALLSETUP_IN 1
/* an outgoing call set up is ongoing */
#define HFP_IND_CALLSETUP_OUT 2
/* remote party being alerted in an outgoing call */
#define HFP_IND_CALLSETUP_OUT_ALERT 3
/*new connected device can keep the lock for 30s*/
#define NEW_CON_TIMEOUT 10
typedef int tLOCK;
#define NEW_CON_LOCK (tLOCK)1<<0
#define A2DP_PLAY_LOCK (tLOCK)1<<1
#define HFP_CALL_LOCK (tLOCK)1<<2
#define HFP_CALLSETUP_LOCK (tLOCK)1<<3
static tLOCK connection_lock = 0;
static guint timer_id = 0;
static pthread_mutex_t dev_mutex;
static void excute_with_lock(void)
{
pthread_mutex_lock(&dev_mutex);
/*if connection_lock set, use strategy 1*/
if (connection_lock)
disconnect_next_dev();
/*if connection_lock cleared, use strategy 2*/
else
disconnect_prev_dev();
pthread_mutex_unlock(&dev_mutex);
}
static void update_lock(int set, tLOCK type)
{
pthread_mutex_lock(&dev_mutex);
info("%s: %s bit%d\n", __func__, (set ? "set": "clear"), type);
if (set) {
/*only if connection_lock not set before*/
if (connection_lock == 0) {
info("%s: acquire lock", __func__);
set_discoverable(0);
set_pairable(0);
}
connection_lock |= type;
} else if (connection_lock != 0){
connection_lock &= ~type;
/*only if connection_lock is completely clear*/
if (connection_lock == 0) {
info("%s: release lock", __func__);
set_discoverable(1);
set_pairable(1);
}
}
info("%s: connection_lock = 0x%x", __func__, connection_lock);
pthread_mutex_unlock(&dev_mutex);
}
static gboolean new_con_cback(void* user_data)
{
info("%s: new connection lock timeout!\n", __func__);
update_lock(0, NEW_CON_LOCK);
return FALSE;
}
static void *hfp_ctl_cb(void *data)
{
struct sockaddr_un address;
int fd, len;
char msg[64] = {0};
init:
memset(&address, 0, sizeof(struct sockaddr_un));
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
error("%s", strerror(errno));
return NULL;
}
address.sun_family = AF_UNIX;
strncpy(address.sun_path, HFP_CTL_SK_PATH, sizeof(address.sun_path));
len = sizeof(address);
if (connect(fd, (struct sockaddr *)&address, len) == -1) {
/*if socket is not avialable or not ready, don't report error*/
if (errno != 111 && errno != 2)
info("connect server: %s", strerror(errno));
sleep(3);
goto init;
}
hfp_ctl_sk = fd;
info("dev_strategy: socket recieving....\n");
while (1) {
memset(msg, 0, sizeof(msg));
len = recv(hfp_ctl_sk, msg, sizeof(msg), 0);
if (len < 0) {
perror("recv");
continue;
}
if (len == 0) {
info("dev_strategy: socket off line\n");
sleep(3);
goto init;
}
if (len == 2)
DBG("event = %d, value = %d\n", msg[0], msg[1]);
else
continue;
switch (msg[0]) {
case HFP_EVENT_CONNECTION:
switch (msg[1]) {
case HFP_IND_DEVICE_DISCONNECTED:
update_lock(0, HFP_CALL_LOCK);
update_lock(0, HFP_CALLSETUP_LOCK);
break;
}
break;
case HFP_EVENT_CALL:
switch (msg[1]) {
case HFP_IND_CALL_NONE:
update_lock(0, HFP_CALL_LOCK);
break;
case HFP_IND_CALL_ACTIVE :
update_lock(1, HFP_CALL_LOCK);
break;
}
break;
case HFP_EVENT_CALLSETUP:
switch (msg[1]) {
case HFP_IND_CALLSETUP_NONE:
update_lock(0, HFP_CALLSETUP_LOCK);
break;
case HFP_IND_CALLSETUP_IN :
case HFP_IND_CALLSETUP_OUT :
update_lock(1, HFP_CALLSETUP_LOCK);
break;
}
break;
}
}
info("exit\n");
return NULL;
}
static int setup_socket(void)
{
info(" ");
if (pthread_create(&phfp_ctl_id, NULL, hfp_ctl_cb, NULL)) {
error("%s", strerror(errno));
return -1;
}
pthread_setname_np(phfp_ctl_id, "hfp_ctl_monitor_dev");
}
static void teardown_socket(void)
{
info(" ");
if (phfp_ctl_id) {
pthread_cancel(phfp_ctl_id);
pthread_detach(phfp_ctl_id);
if (hfp_ctl_sk)
close(hfp_ctl_sk);
phfp_ctl_id = 0;
hfp_ctl_sk = 0;
}
}
#endif //DEV_STRATEGY == 3
static void save_reserved_device()
{
FILE *fp;
const char *obj_path;
if (!reserved_device_proxy) {
info("reserved_device_proxy is NULL");
return;
}
obj_path = g_dbus_proxy_get_path(reserved_device_proxy);
info("reserved device obj_path = %s\n", obj_path);
fp = fopen(RESERVED_DEVICE_FILE, "w");
if (fp == NULL) {
error("open %s: %s", RESERVED_DEVICE_FILE, strerror(errno));
return;
}
fwrite(obj_path, strlen(obj_path), 1, fp);
fclose(fp);
}
static const char* get_reserved_device()
{
FILE *fp;
static char obj_path[] = "/org/bluez/hci0/dev_xx_xx_xx_xx_xx_xx";
size_t ret;
fp = fopen(RESERVED_DEVICE_FILE, "r");
if (fp == NULL) {
error("open %s: %s", RESERVED_DEVICE_FILE, strerror(errno));
return obj_path;
}
ret = fread(obj_path, sizeof(obj_path), 1, fp);
if (ret == 0) {
error("read reserved device failed\n");
}
fclose(fp);
info("reserved device obj_path readed = %s\n", obj_path);
return obj_path;
}
static int set_discoverable(int enable)
{
dbus_bool_t value;
DBusMessageIter iter;
if (!adapter_proxy) {
error("Enable/Disable disacoverable fail, org.bluez.Adapter1 not registered yet!");
return -1;
}
if (enable) {
info("Set local device discoverable");
value = TRUE;
} else {
info("Set local device undiscoverable");
value = FALSE;
}
if (!g_dbus_proxy_set_property_basic(adapter_proxy,
"Discoverable",
DBUS_TYPE_BOOLEAN,
&value,
NULL,
NULL,
NULL))
error("failed to set discoverable property");
return 0;
}
static int set_pairable(int enable)
{
dbus_bool_t value;
DBusMessageIter iter;
if (!adapter_proxy) {
error("Enable/Disable pairable fail, org.bluez.Adapter1 not registered yet!");
return -1;
}
if (enable) {
info("Set local device pairable");
value = TRUE;
} else {
info("Set local device unpairable");
value = FALSE;
}
if (!g_dbus_proxy_set_property_basic(adapter_proxy,
"Pairable",
DBUS_TYPE_BOOLEAN,
&value,
NULL,
NULL,
NULL))
error("failed to set pairable property");
return 0;
}
static void connect_dev_return(DBusMessage *message, void *user_data)
{
DBusError error;
static int cnt = 3;
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, message) == TRUE) {
info("%s get msg: %s", __func__, error.name);
if (strstr(error.name, "Failed") && (cnt > 0)) {
info("%s reset timer", __func__);
cnt--;
g_timeout_add_seconds(SRT[1], connect_dev, user_data);
}
dbus_error_free(&error);
}
}
static gboolean connect_dev(void* user_data)
{
GDBusProxy *proxy = (GDBusProxy *) user_data;
DBusMessageIter iter;
info("Startup reconnection begin!!");
if (!proxy) {
error("Connecet device fail, invalid proxy!");
return FALSE;
}
if (g_list_length(dev_list) > 0) {
error("Device connected already, return!");
return FALSE;
}
info("Connect target device: %s", g_dbus_proxy_get_path(proxy));
if (g_dbus_proxy_method_call(proxy,
"Connect",
NULL,
connect_dev_return,
user_data,
NULL) == FALSE) {
error("Failed to call org.bluez.Device1.Connect");
}
//not matter what, stop timer
return FALSE;
}
static int disconnect_prev_dev()
{
DBusMessageIter iter;
GList *temp = g_list_last(dev_list)->prev;
info("Disconnect earlier connected device");
while (temp) {
GDBusProxy *proxy = (GDBusProxy *)temp->data;
info("Disconnecting device, obj_path = %s\n", g_dbus_proxy_get_path(proxy));
if (g_dbus_proxy_method_call(proxy,
"Disconnect",
NULL,
NULL,
NULL,
NULL) == FALSE) {
error("Failed to call org.bluez.Device1.Disconnect");
}
temp = temp->prev;
}
return 0;
}
static int disconnect_next_dev()
{
DBusMessageIter iter;
GList *temp = g_list_first(dev_list)->next;
info("Disconnect later connected device");
while (temp) {
GDBusProxy *proxy = (GDBusProxy *)temp->data;
info("Disconnecting device, obj_path = %s\n", g_dbus_proxy_get_path(proxy));
if (g_dbus_proxy_method_call(proxy,
"Disconnect",
NULL,
NULL,
NULL,
NULL) == FALSE) {
error("Failed to call org.bluez.Device1.Disconnect");
}
temp = temp->next;
}
return 0;
}
static int strategy_excute()
{
int dev_num = g_list_length(dev_list);
info("Connected device number = %d", dev_num);
#if (DEV_STRATEGY == 1)
if (dev_num > 1)
disconnect_next_dev();
if (dev_num > 0)
set_discoverable(0);
else
set_discoverable(1);
#elif (DEV_STRATEGY == 2)
if (dev_num > 1)
disconnect_prev_dev();
#elif (DEV_STRATEGY == 3)
if (dev_num == 0) {
/*clear all lock*/
update_lock(0, NEW_CON_LOCK | A2DP_PLAY_LOCK | HFP_CALL_LOCK | HFP_CALLSETUP_LOCK);
/*if new_con timer still running, we should turn it off*/
if (timer_id) {
GSource *s = g_main_context_find_source_by_id(NULL, timer_id);
if (s != NULL) {
g_source_destroy(s);
timer_id = 0;
}
}
} else if (dev_num > 1) {
excute_with_lock();
}
#endif
if (dev_num == 1) {
reserved_device_proxy = (GDBusProxy *)g_list_first(dev_list)->data;
save_reserved_device();
}
return 0;
}
static void property_changed(GDBusProxy *proxy, const char *name,
DBusMessageIter *iter, void *user_data)
{
const char *interface, *status;
dbus_bool_t valbool;
interface = g_dbus_proxy_get_interface(proxy);
if (!strcmp(interface, "org.bluez.Device1")) {
if (!strcmp(name, "Connected")) {
dbus_message_iter_get_basic(iter, &valbool);
info("%s, Connectd status changed: %s\n", g_dbus_proxy_get_path(proxy), valbool == TRUE ? "TRUE" : "FALSE");
if (TRUE == valbool) {
dev_list = g_list_append(dev_list, proxy);
} else {
dev_list = g_list_remove(dev_list, proxy);
}
strategy_excute();
}
}
if (!strcmp(interface, "org.bluez.Adapter1")) {
if (!strcmp(name, "Powered")) {
dbus_message_iter_get_basic(iter, &valbool);
if (TRUE == valbool && reserved_device_proxy) {
info("Going to reconnect last connected device!");
//create a timer call connect_dev later
g_timeout_add_seconds(SRT[1], connect_dev, (void *)reserved_device_proxy);
}
}
}
#if (DEV_STRATEGY == 3)
if (!strcmp(interface, "org.bluez.MediaControl1")) {
if (!strcmp(name, "Connected")) {
dbus_message_iter_get_basic(iter, &valbool);
info("%s, A2dp Connectd status changed: %s\n", g_dbus_proxy_get_path(proxy), valbool == TRUE ? "TRUE" : "FALSE");
if (TRUE == valbool) {
GSource *s = NULL;
if (timer_id)
s = g_main_context_find_source_by_id(NULL, timer_id);
/*if prv-timer is still runnig, destroy it first*/
if (s)
g_source_destroy(s);
/*use a timer to unlock later*/
timer_id = g_timeout_add_seconds(NEW_CON_TIMEOUT, new_con_cback, NULL);
update_lock(1, NEW_CON_LOCK);
}
}
}
if (!strcmp(interface, "org.bluez.MediaPlayer1")) {
if (!strcmp(name, "Status")) {
dbus_message_iter_get_basic(iter, &status);
info("%s, A2dp play status changed: %s\n", g_dbus_proxy_get_path(proxy), status);
if (!strcmp(status, "playing"))
update_lock(1, A2DP_PLAY_LOCK);
else
update_lock(0, A2DP_PLAY_LOCK);
}
}
#endif //DEV_STRATEGY == 3
}
static void proxy_added(GDBusProxy *proxy, void *user_data)
{
const char *interface;
DBusMessageIter iter;
dbus_bool_t valbool;
static int startup = 1;
interface = g_dbus_proxy_get_interface(proxy);
if (!strcmp(interface, "org.bluez.Device1")) {
const char *obj_path = g_dbus_proxy_get_path(proxy);
info("org.bluez.Device1 registered: %s\n", obj_path);
if (TRUE == g_dbus_proxy_get_property(proxy, "Connected", &iter)) {
dbus_message_iter_get_basic(&iter, &valbool);
if (TRUE == valbool) {
dev_list = g_list_append(dev_list, proxy);
strategy_excute();
} else {
/*If device is found in startup and is reserved_device
we will reconnect it*/
if (!strcmp(obj_path, reserved_device_path) && startup) {
info("Going to reconnect last connected device!");
startup = 0;
//create a timer call connect_dev later
g_timeout_add_seconds(SRT[0], connect_dev, (void *)proxy);
}
}
}
}
if (!strcmp(interface, "org.bluez.Adapter1"))
adapter_proxy = proxy;
}
static void proxy_removed(GDBusProxy *proxy, void *user_data)
{
const char *interface;
DBusMessageIter iter;
interface = g_dbus_proxy_get_interface(proxy);
if (!strcmp(interface, "org.bluez.Device1")) {
info("org.bluez.Device1 removed: %s\n", g_dbus_proxy_get_path(proxy));
}
if (!strcmp(interface, "org.bluez.Adapter1"))
adapter_proxy = NULL;
}
static int dev_strategy_probe(struct btd_adapter *adapter)
{
DBG("");
g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed, property_changed, NULL);
return 0;
}
static void dev_strategy_remove(struct btd_adapter *adapter)
{
DBG("");
}
static struct btd_adapter_driver dev_strategy_driver = {
.name = "dev_strategy",
.probe = dev_strategy_probe,
.remove = dev_strategy_remove,
};
static int dev_strategy_init(void)
{
int err = 0;
#if (DEV_STRATEGY > 0)
DBusConnection *conn = btd_get_dbus_connection();
DBG("%s", __func__);
info("dev_strategy%d starting work!!", DEV_STRATEGY);
client = g_dbus_client_new(conn, "org.bluez", "/org/bluez");
err = btd_register_adapter_driver(&dev_strategy_driver);
if (err < 0) {
g_dbus_client_unref(client);
error("Failed to register btd driver\n");
}
reserved_device_path = get_reserved_device();
#if (DEV_STRATEGY == 3)
setup_socket();
#endif
#else
info("dev_strategy won't work!!");
#endif
return err;
}
static void dev_strategy_exit(void)
{
DBG("%s", __func__);
#if (DEV_STRATEGY > 0)
btd_unregister_adapter_driver(&dev_strategy_driver);
if (client) {
g_dbus_client_unref(client);
client = NULL;
}
if (dev_list) {
g_list_free(dev_list);
dev_list = NULL;
}
#if (DEV_STRATEGY == 3)
teardown_socket();
#endif
#endif
}
BLUETOOTH_PLUGIN_DEFINE(dev_strategy, VERSION,
BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, dev_strategy_init, dev_strategy_exit)